diff --git a/apps/docs/src/guide/encoding/encode-and-decode.md b/apps/docs/src/guide/encoding/encode-and-decode.md index 6ed76fad5ad..561e7feeaa1 100644 --- a/apps/docs/src/guide/encoding/encode-and-decode.md +++ b/apps/docs/src/guide/encoding/encode-and-decode.md @@ -1,13 +1,13 @@ # Encode and Decode -To interact with the FuelVM, types must be encoded and decoded per the [argument encoding specification](https://docs.fuel.network/docs/specs/abi/argument-encoding/). The SDK provides the `Interface` class to encode and decode data. +To interact with the FuelVM, types must be encoded and decoded per the [argument encoding specification](https://docs.fuel.network/docs/specs/abi/argument-encoding/). The SDK provides the `AbiCoder` class to encode and decode data. -The relevant methods of `Interface` are: +To encode and decode types, the `AbiCoder` class provides the `getType` method which returns a `AbiCoderType` instance that provides the following methods: -- `encodeType` -- `decodeType` +- `encode` +- `decode` -The `Interface` class requires you to pass the [ABI](https://docs.fuel.network/docs/specs/abi/json-abi-format/) on initialization. Both methods accept a `concreteTypeId`, which must exist in the ABI's `concreteTypes` array. After that, a suitable coder will be assigned to encode/decode that type. +The `AbiCoder` class requires you to pass the [ABI](https://docs.fuel.network/docs/specs/abi/json-abi-format/) on initialization. Both methods accept a `concreteTypeId`, which must exist in the ABI's `concreteTypes` array. After that, a suitable coder will be assigned to encode/decode that type. Imagine we are working with the following script that returns the sum of two `u32` integers: @@ -23,7 +23,7 @@ It will produce the following ABI: <<< @./snippets/encode-and-decode.jsonc#encode-and-decode-2{json:line-numbers} -Now, let's prepare some data to pass to the `main` function to retrieve the combined integer. The function expects and returns a `u32` integer. So here, we will encode the `u32` to pass it to the function and receive the same `u32` back, as bytes, that we'll use for decoding. We can do both of these with the `Interface`. +Now, let's prepare some data to pass to the `main` function to retrieve the combined integer. The function expects and returns a `u32` integer. So here, we will encode the `u32` to pass it to the function and receive the same `u32` back, as bytes, that we'll use for decoding. We can do both of these with the `AbiCoder`. First, let's prepare the transaction: diff --git a/apps/docs/src/guide/encoding/snippets/encode-and-decode.ts b/apps/docs/src/guide/encoding/snippets/encode-and-decode.ts index 785d379b190..2affe137dc0 100644 --- a/apps/docs/src/guide/encoding/snippets/encode-and-decode.ts +++ b/apps/docs/src/guide/encoding/snippets/encode-and-decode.ts @@ -1,12 +1,15 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ // #region full -import type { JsonAbi, TransactionResultReturnDataReceipt } from 'fuels'; +import type { + AbiSpecification, + TransactionResultReturnDataReceipt, +} from 'fuels'; import { buildFunctionResult, ReceiptType, arrayify, Script, - Interface, + AbiCoder, Provider, Wallet, } from 'fuels'; @@ -20,7 +23,7 @@ const wallet = Wallet.fromPrivateKey(WALLET_PVT_KEY, provider); // First we need to build out the transaction via the script that we want to encode. // For that we'll need the ABI and the bytecode of the script -const abi: JsonAbi = ScriptSum.abi; +const abi: AbiSpecification = ScriptSum.abi; const bytecode = ScriptSum.bytecode; // Create the invocation scope for the script call, passing the initial @@ -43,13 +46,13 @@ const argument = abi.functions .find((f) => f.name === 'main') ?.inputs.find((i) => i.name === 'inputted_amount')?.concreteTypeId as string; -// The `Interface` class (imported from `fuels`) is the entry point for encoding and decoding all things abi-related. -// We will use its `encodeType` method and create the encoding required for -// a u32 which takes 4 bytes up of property space. +// The `AbiCoder` class (imported from `fuels`) is the entry point for encoding and decoding all things abi-related. +// We will use its `getType` method to get the coder for the argument and then call its `encode` method to +// create the encoding required for a u32 which takes 4 bytes up of property space. -const abiInterface = new Interface(abi); +const abiInterface = AbiCoder.fromAbi(abi); const argumentToAdd = 10; -const encodedArguments = abiInterface.encodeType(argument, [argumentToAdd]); +const encodedArguments = abiInterface.getType(argument).encode([argumentToAdd]); // Therefore the value of 10 will be encoded to: // Uint8Array([0, 0, 0, 10] @@ -94,10 +97,13 @@ const returnData = arrayify(returnDataReceipt.data); // returnData = new Uint8Array([0, 0, 0, 20] // And now we can decode the returned bytes in a similar fashion to how they were -// encoded, via the `Interface` -const [decodedReturnData] = abiInterface.decodeType(argument, returnData); +// encoded, via the `AbiCoder` +const decodedReturnData = abiInterface.getType(argument).decode(returnData); // 20 const totalValue = argumentToAdd + initialValue; // #endregion encode-and-decode-5 // #endregion full + +console.log('decodedReturnData should be 20', decodedReturnData === 20); +console.log('totalValue should be 20', totalValue === 20); diff --git a/apps/docs/src/guide/encoding/snippets/working-with-bytes.ts b/apps/docs/src/guide/encoding/snippets/working-with-bytes.ts index 1dc89c369dd..58897cb6c8d 100644 --- a/apps/docs/src/guide/encoding/snippets/working-with-bytes.ts +++ b/apps/docs/src/guide/encoding/snippets/working-with-bytes.ts @@ -1,42 +1,26 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ // #region full import { randomBytes } from 'crypto'; -import { - ArrayCoder, - B256Coder, - B512Coder, - BigNumberCoder, - BooleanCoder, - EnumCoder, - NumberCoder, - RawSliceCoder, - StdStringCoder, - StringCoder, - StructCoder, - TupleCoder, - VecCoder, - hexlify, -} from 'fuels'; +import { AbiEncoding, hexlify } from 'fuels'; // #region working-with-bytes-1 -const u8Coder = new NumberCoder('u8'); +const u8Coder = AbiEncoding.v1.coders.u8; const encodedU8 = u8Coder.encode(255); -const u16Coder = new NumberCoder('u16'); +const u16Coder = AbiEncoding.v1.coders.u16; const encodedU16 = u16Coder.encode(255); -const u32Coder = new NumberCoder('u32'); +const u32Coder = AbiEncoding.v1.coders.u32; const encodedU32 = u32Coder.encode(255); -const u64Coder = new BigNumberCoder('u64'); +const u64Coder = AbiEncoding.v1.coders.u64; const encodedU64 = u64Coder.encode(255); -const u256Coder = new BigNumberCoder('u256'); +const u256Coder = AbiEncoding.v1.coders.u256; const encodedU256 = u256Coder.encode(255); // #endregion working-with-bytes-1 // #region working-with-bytes-2 -const booleanCoder = new BooleanCoder(); +const booleanCoder = AbiEncoding.v1.coders.bool; const encodedTrue = booleanCoder.encode(true); const encodedFalse = booleanCoder.encode(false); @@ -44,45 +28,109 @@ const encodedFalse = booleanCoder.encode(false); // #endregion working-with-bytes-2 // #region working-with-bytes-3 -const stringCoder = new StringCoder(5); -const encoded = stringCoder.encode('hello'); +const stringCoder = AbiEncoding.v1.coders.string({ encodedLength: 5 }); +const encodedString = stringCoder.encode('hello'); // #endregion working-with-bytes-3 // #region working-with-bytes-4 -const b256Coder = new B256Coder(); +const b256Coder = AbiEncoding.v1.coders.b256; const encodedB256 = b256Coder.encode(hexlify(randomBytes(32))); -const b512Coder = new B512Coder(); +const b512Coder = AbiEncoding.v1.coders.b512; const encodedB512 = b512Coder.encode(hexlify(randomBytes(64))); // #endregion working-with-bytes-4 // #region working-with-bytes-5 -const tupleCoder = new TupleCoder([ - new NumberCoder('u8'), - new NumberCoder('u16'), -]); +const tupleCoder = AbiEncoding.v1.coders.tuple({ + coders: [AbiEncoding.v1.coders.u8, AbiEncoding.v1.coders.u16], +}); const encodedTuple = tupleCoder.encode([255, 255]); -const structCoder = new StructCoder('struct', { - a: new NumberCoder('u8'), - b: new NumberCoder('u16'), +const structCoder = AbiEncoding.v1.coders.struct({ + coders: { + a: AbiEncoding.v1.coders.u8, + b: AbiEncoding.v1.coders.u16, + }, }); const encodedStruct = structCoder.encode({ a: 255, b: 255 }); -const arrayCoder = new ArrayCoder(new NumberCoder('u8'), 4); +const arrayCoder = AbiEncoding.v1.coders.array({ + coder: AbiEncoding.v1.coders.u8, + size: 4, +}); const encodedArray = arrayCoder.encode([255, 0, 255, 0]); -const enumCoder = new EnumCoder('enum', { a: new NumberCoder('u32') }); +const enumCoder = AbiEncoding.v1.coders.enum({ + coders: { a: AbiEncoding.v1.coders.u32 }, +}); const encodedEnum = enumCoder.encode({ a: 255 }); // #endregion working-with-bytes-5 // #region working-with-bytes-6 -const vecCoder = new VecCoder(new NumberCoder('u8')); +const vecCoder = AbiEncoding.v1.coders.vector({ + coder: AbiEncoding.v1.coders.u8, +}); const encodedVec = vecCoder.encode([255, 0, 255]); -const stdStringCoder = new StdStringCoder(); +const stdStringCoder = AbiEncoding.v1.coders.stdString; const encodedStdString = stdStringCoder.encode('hello'); -const rawSliceCoder = new RawSliceCoder(); +const rawSliceCoder = AbiEncoding.v1.coders.rawSlice; const encodedRawSlice = rawSliceCoder.encode([1, 2, 3, 4]); // #endregion working-with-bytes-6 // #endregion full + +console.log('encodedU8 should be [255]', encodedU8.toString() === '255'); +console.log('encodedU16 should be [0, 255]', encodedU16.toString() === '0,255'); +console.log( + 'encodedU32 should be [0, 0, 0, 255]', + encodedU32.toString() === '0,0,0,255' +); +console.log( + 'encodedU64 should be [0, 0, 0, 0, 0, 0, 0, 255]', + encodedU64.toString() === '0,0,0,0,0,0,0,255' +); +console.log( + 'encodedU256 should be [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255]', + encodedU256.toString() === + '0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255' +); + +console.log('encodedTrue should be [1]', encodedTrue.toString() === '1'); +console.log('encodedFalse should be [0]', encodedFalse.toString() === '0'); + +console.log( + 'encodedString should be [104, 101, 108, 108, 111]', + encodedString.toString() === '104,101,108,108,111' +); + +console.log('encodedB256 should be 32', encodedB256.length === 32); +console.log('encodedB512 should be 64', encodedB512.length === 64); + +console.log( + 'encodedTuple should be [255, 0, 255]', + encodedTuple.toString() === '255,0,255' +); +console.log( + 'encodedStruct should be [255, 0, 255]', + encodedStruct.toString() === '255,0,255' +); +console.log( + 'encodedArray should be [255, 0, 255, 0]', + encodedArray.toString() === '255,0,255,0' +); +console.log( + 'encodedEnum should be [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255]', + encodedEnum.toString() === '0,0,0,0,0,0,0,0,0,0,0,255' +); +console.log( + 'encodedVec should be [0, 0, 0, 0, 0, 0, 0, 3, 255, 0, 255]', + encodedVec.toString() === '0,0,0,0,0,0,0,3,255,0,255' +); +console.log( + 'encodedStdString should be [0, 0, 0, 0, 0, 0, 0, 5, 104, 101, 108, 108, 111]', + encodedStdString.toString() === '0,0,0,0,0,0,0,5,104,101,108,108,111' +); +console.log( + 'encodedRawSlice should be [0, 0, 0, 0, 0, 0, 0, 4, 1, 2, 3, 4]', + encodedRawSlice.toString() === '0,0,0,0,0,0,0,4,1,2,3,4' +); diff --git a/apps/docs/src/guide/encoding/working-with-bytes.md b/apps/docs/src/guide/encoding/working-with-bytes.md index a11ec7a6f51..81fa6aca4f6 100644 --- a/apps/docs/src/guide/encoding/working-with-bytes.md +++ b/apps/docs/src/guide/encoding/working-with-bytes.md @@ -6,7 +6,7 @@ This guide aims to give a high-level overview of how to work with bytes in the S We know the sizes of all core types at compile time. They are the building blocks of the more complex types and are the most common types you will encounter. -### Unsigned Integer (`u8` / `u16` / `u32` / `u64` / `u128` / `u256`) +### Unsigned Integer (`u8` / `u16` / `u32` / `u64` / `u256`) Each type will only contain the number of bits specified in the name. For example, a `u8` will contain 8 bits, and a `u256` will contain 256 bits and take up the exact property space with no additional padding. diff --git a/apps/docs/src/guide/scripts/snippets/initialising-scripts.ts b/apps/docs/src/guide/scripts/snippets/initialising-scripts.ts index 8b98087fb4c..b30e9e3173b 100644 --- a/apps/docs/src/guide/scripts/snippets/initialising-scripts.ts +++ b/apps/docs/src/guide/scripts/snippets/initialising-scripts.ts @@ -29,10 +29,9 @@ const scriptRequest = new ScriptRequest( throw new Error('fail'); } - const [decodedResult] = script.interface.functions.main.decodeOutput( + return script.interface.functions.main.decodeOutput( scriptResult.returnReceipt.data ); - return decodedResult; } ); // #endregion script-init diff --git a/packages/abi-coder/src/FunctionFragment.ts b/packages/abi-coder/src/FunctionFragment.ts index c44c9d2d4eb..0ac0ffcd175 100644 --- a/packages/abi-coder/src/FunctionFragment.ts +++ b/packages/abi-coder/src/FunctionFragment.ts @@ -75,7 +75,7 @@ export class FunctionFragment { return new TupleCoder(coders).encode(argumentValues); } - decodeArguments(data: BytesLike) { + decodeArguments(data: BytesLike): unknown[] | undefined { const bytes = arrayify(data); const nonVoidInputs = findNonVoidInputs(this.jsonAbiOld, this.jsonFnOld.inputs); diff --git a/packages/abi-typegen/src/templates/contract/main.hbs b/packages/abi-typegen/src/templates/contract/main.hbs index d4285148271..e72faa55921 100644 --- a/packages/abi-typegen/src/templates/contract/main.hbs +++ b/packages/abi-typegen/src/templates/contract/main.hbs @@ -1,12 +1,13 @@ {{header}} -import { Contract, Interface } from "fuels"; +import { Contract, AbiCoder } from "fuels"; {{#if imports}} import type { Provider, Account, StorageSlot, AbstractAddress, + AbiSpecification, {{#each imports}} {{this}}, {{/each}} @@ -53,18 +54,14 @@ export type {{capitalizedName}}Configurables = Partial<{ }>; {{/if}} -const abi = {{abiJsonString}}; +const abi: AbiSpecification = {{abiJsonString}}; const storageSlots: StorageSlot[] = {{storageSlotsJsonString}}; -export class {{capitalizedName}}Interface extends Interface { - constructor() { - super(abi); - } - +export class {{capitalizedName}}AbiCoder extends AbiCoder { declare functions: { {{#each functionsFragments}} - {{this}}: FunctionFragment; + {{this}}: AbiCoderFunction; {{/each}} }; } @@ -73,7 +70,7 @@ export class {{capitalizedName}} extends Contract { static readonly abi = abi; static readonly storageSlots = storageSlots; - declare interface: {{capitalizedName}}Interface; + declare interface: {{capitalizedName}}AbiCoder; declare functions: { {{#each functionsTypedefs}} {{this}}; diff --git a/packages/abi-typegen/src/templates/contract/main.ts b/packages/abi-typegen/src/templates/contract/main.ts index 759f3624f78..258296f8061 100644 --- a/packages/abi-typegen/src/templates/contract/main.ts +++ b/packages/abi-typegen/src/templates/contract/main.ts @@ -32,7 +32,7 @@ export function renderMainTemplate(params: { abi: Abi; versions: BinaryVersions const { structs } = formatStructs({ types }); const { imports } = formatImports({ types, - baseMembers: ['FunctionFragment', 'InvokeFunction'], + baseMembers: ['AbiCoderFunction', 'InvokeFunction'], }); const { rawContents, storageSlotsContents } = params.abi; diff --git a/packages/abi-typegen/src/templates/predicate/main.hbs b/packages/abi-typegen/src/templates/predicate/main.hbs index 03cbaf76dd6..56f68ed8a79 100644 --- a/packages/abi-typegen/src/templates/predicate/main.hbs +++ b/packages/abi-typegen/src/templates/predicate/main.hbs @@ -57,7 +57,7 @@ export type {{capitalizedName}}Parameters = Omit< 'abi' | 'bytecode' >; -const abi = {{abiJsonString}}; +const abi: AbiSpecification = {{abiJsonString}}; const bytecode = decompressBytecode('{{compressedBytecode}}'); diff --git a/packages/abi-typegen/src/templates/predicate/main.ts b/packages/abi-typegen/src/templates/predicate/main.ts index a2c928ecf94..01e0fee0134 100644 --- a/packages/abi-typegen/src/templates/predicate/main.ts +++ b/packages/abi-typegen/src/templates/predicate/main.ts @@ -34,7 +34,14 @@ export function renderMainTemplate(params: { abi: Abi; versions: BinaryVersions const { structs } = formatStructs({ types }); const { imports } = formatImports({ types, - baseMembers: ['Predicate', 'Provider', 'InputValue', 'PredicateParams', 'decompressBytecode'], + baseMembers: [ + 'Predicate', + 'Provider', + 'InputValue', + 'PredicateParams', + 'decompressBytecode', + 'AbiSpecification', + ], }); const { prefixedInputs: inputs, output } = func.attributes; diff --git a/packages/abi-typegen/src/templates/script/main.hbs b/packages/abi-typegen/src/templates/script/main.hbs index 225d540e9ca..403aec80ba3 100644 --- a/packages/abi-typegen/src/templates/script/main.hbs +++ b/packages/abi-typegen/src/templates/script/main.hbs @@ -51,7 +51,7 @@ export type {{capitalizedName}}Configurables = Partial<{ }>; {{/if}} -const abi = {{abiJsonString}}; +const abi: AbiSpecification = {{abiJsonString}}; const bytecode = decompressBytecode('{{compressedBytecode}}'); diff --git a/packages/abi-typegen/src/templates/script/main.ts b/packages/abi-typegen/src/templates/script/main.ts index 36b76d2ce74..3ce3e15615a 100644 --- a/packages/abi-typegen/src/templates/script/main.ts +++ b/packages/abi-typegen/src/templates/script/main.ts @@ -34,7 +34,7 @@ export function renderMainTemplate(params: { abi: Abi; versions: BinaryVersions const { structs } = formatStructs({ types }); const { imports } = formatImports({ types, - baseMembers: ['Script', 'Account', 'decompressBytecode'], + baseMembers: ['Script', 'Account', 'decompressBytecode', 'AbiSpecification'], }); const { prefixedInputs: inputs, output } = func.attributes; diff --git a/packages/abi-typegen/test/fixtures/forc-projects/full/src/main.sw b/packages/abi-typegen/test/fixtures/forc-projects/full/src/main.sw index 500f8639108..74af47c4e97 100644 --- a/packages/abi-typegen/test/fixtures/forc-projects/full/src/main.sw +++ b/packages/abi-typegen/test/fixtures/forc-projects/full/src/main.sw @@ -81,15 +81,15 @@ abi MyContract { fn types_vector_geo(x: Vec) -> Vec; fn types_vector_option(x: Vec) -> Vec; fn types_option(x: Option) -> Option; - fn types_option_geo(x: Option) -> Option; + fn types_option_struct(x: Option) -> Option; fn types_evm_address(x: EvmAddress) -> EvmAddress; fn types_bytes(x: Bytes) -> Bytes; fn types_raw_slice(x: raw_slice) -> raw_slice; fn types_str_slice(x: str) -> str; fn types_std_string(x: String) -> String; fn types_result(x: Result) -> Result; - fn type_address(x: Address) -> Address; - fn type_contract_id(x: ContractId) -> ContractId; + fn types_address(x: Address) -> Address; + fn types_contract_id(x: ContractId) -> ContractId; fn type_identity(x: Identity) -> Identity; fn type_external_struct(x: ExternalStruct) -> ExternalStruct; fn type_external_enum(x: ExternalEnum) -> ExternalEnum; @@ -182,7 +182,7 @@ impl MyContract for Contract { fn types_option(x: Option) -> Option { x } - fn types_option_geo(x: Option) -> Option { + fn types_option_struct(x: Option) -> Option { x } fn types_evm_address(x: EvmAddress) -> EvmAddress { @@ -211,10 +211,10 @@ impl MyContract for Contract { Err(MyContractError::DivisionByZero) => Err(__to_str_array("DivisError")), } } - fn type_address(x: Address) -> Address { + fn types_address(x: Address) -> Address { x } - fn type_contract_id(x: ContractId) -> ContractId { + fn types_contract_id(x: ContractId) -> ContractId { x } fn type_identity(x: Identity) -> Identity { diff --git a/packages/abi-typegen/test/fixtures/templates/contract-with-configurable/main.hbs b/packages/abi-typegen/test/fixtures/templates/contract-with-configurable/main.hbs index d767304335a..7ced7c958f5 100644 --- a/packages/abi-typegen/test/fixtures/templates/contract-with-configurable/main.hbs +++ b/packages/abi-typegen/test/fixtures/templates/contract-with-configurable/main.hbs @@ -10,14 +10,15 @@ Fuel-Core version: 33.33.33 */ -import { Contract, Interface } from "fuels"; +import { Contract, AbiCoder } from "fuels"; import type { Provider, Account, StorageSlot, AbstractAddress, + AbiSpecification, + AbiCoderFunction, BigNumberish, - FunctionFragment, InvokeFunction, } from 'fuels'; @@ -32,7 +33,7 @@ export type MyContractConfigurables = Partial<{ A_GENERIC_STRUCT: GenericStructInput, BigNumberish>; }>; -const abi = { +const abi: AbiSpecification = { "programType": "contract", "specVersion": "1", "encodingVersion": "1", @@ -177,13 +178,9 @@ const abi = { const storageSlots: StorageSlot[] = []; -export class MyContractInterface extends Interface { - constructor() { - super(abi); - } - +export class MyContractAbiCoder extends AbiCoder { declare functions: { - main: FunctionFragment; + main: AbiCoderFunction; }; } @@ -191,7 +188,7 @@ export class MyContract extends Contract { static readonly abi = abi; static readonly storageSlots = storageSlots; - declare interface: MyContractInterface; + declare interface: MyContractAbiCoder; declare functions: { main: InvokeFunction<[x: string, y: string], boolean>; }; diff --git a/packages/abi-typegen/test/fixtures/templates/contract/main.hbs b/packages/abi-typegen/test/fixtures/templates/contract/main.hbs index 942b70a5042..9356ca373fd 100644 --- a/packages/abi-typegen/test/fixtures/templates/contract/main.hbs +++ b/packages/abi-typegen/test/fixtures/templates/contract/main.hbs @@ -10,17 +10,18 @@ Fuel-Core version: 33.33.33 */ -import { Contract, Interface } from "fuels"; +import { Contract, AbiCoder } from "fuels"; import type { Provider, Account, StorageSlot, AbstractAddress, + AbiSpecification, + AbiCoderFunction, BigNumberish, BN, Bytes, EvmAddress, - FunctionFragment, InvokeFunction, RawSlice, StdString, @@ -55,7 +56,7 @@ export type MyStructOutput = { x: number, y: number, state: MyEnumOutput }; export type StructWithMultiOptionInput = { multiple: [Option, Option, Option, Option, Option] }; export type StructWithMultiOptionOutput = { multiple: [Option, Option, Option, Option, Option] }; -const abi = { +const abi: AbiSpecification = { "programType": "contract", "specVersion": "1", "encodingVersion": "1", @@ -685,28 +686,6 @@ const abi = { "output": "a95e1fcceb1451b8a76471f593f66c4a52ca04bde3c227c746ad7aaf988de5c6", "attributes": null }, - { - "inputs": [ - { - "name": "x", - "concreteTypeId": "f597b637c3b0f588fb8d7086c6f4735caa3122b85f0423b82e489f9bb58e2308" - } - ], - "name": "type_address", - "output": "f597b637c3b0f588fb8d7086c6f4735caa3122b85f0423b82e489f9bb58e2308", - "attributes": null - }, - { - "inputs": [ - { - "name": "x", - "concreteTypeId": "29c10735d33b5159f0c71ee1dbd17b36a3e69e41f00fab0d42e1bd9f428d8a54" - } - ], - "name": "type_contract_id", - "output": "29c10735d33b5159f0c71ee1dbd17b36a3e69e41f00fab0d42e1bd9f428d8a54", - "attributes": null - }, { "inputs": [ { @@ -740,6 +719,17 @@ const abi = { "output": "ab7cd04e05be58e3fc15d424c2c4a57f824a2a2d97d67252440a3925ebdc1335", "attributes": null }, + { + "inputs": [ + { + "name": "x", + "concreteTypeId": "f597b637c3b0f588fb8d7086c6f4735caa3122b85f0423b82e489f9bb58e2308" + } + ], + "name": "types_address", + "output": "f597b637c3b0f588fb8d7086c6f4735caa3122b85f0423b82e489f9bb58e2308", + "attributes": null + }, { "inputs": [ { @@ -806,6 +796,17 @@ const abi = { "output": "cdd87b7d12fe505416570c294c884bca819364863efe3bf539245fa18515fbbb", "attributes": null }, + { + "inputs": [ + { + "name": "x", + "concreteTypeId": "29c10735d33b5159f0c71ee1dbd17b36a3e69e41f00fab0d42e1bd9f428d8a54" + } + ], + "name": "types_contract_id", + "output": "29c10735d33b5159f0c71ee1dbd17b36a3e69e41f00fab0d42e1bd9f428d8a54", + "attributes": null + }, { "inputs": [ { @@ -905,7 +906,7 @@ const abi = { "concreteTypeId": "3597e0782bd4dbaf5c8025b40ff3a325845ee34caa713a6d664bda034a31d02a" } ], - "name": "types_option_geo", + "name": "types_option_struct", "output": "3597e0782bd4dbaf5c8025b40ff3a325845ee34caa713a6d664bda034a31d02a", "attributes": null }, @@ -1150,52 +1151,48 @@ const abi = { const storageSlots: StorageSlot[] = []; -export class MyContractInterface extends Interface { - constructor() { - super(abi); - } - +export class MyContractAbiCoder extends AbiCoder { declare functions: { - alias_types_tuple_with_native_types: FunctionFragment; - type_address: FunctionFragment; - type_contract_id: FunctionFragment; - type_external_enum: FunctionFragment; - type_external_struct: FunctionFragment; - type_identity: FunctionFragment; - types_array: FunctionFragment; - types_asset_id: FunctionFragment; - types_b256: FunctionFragment; - types_b512: FunctionFragment; - types_bool: FunctionFragment; - types_bytes: FunctionFragment; - types_empty: FunctionFragment; - types_empty_then_value: FunctionFragment; - types_enum: FunctionFragment; - types_enum_with_vector: FunctionFragment; - types_evm_address: FunctionFragment; - types_generic_enum: FunctionFragment; - types_generic_struct: FunctionFragment; - types_option: FunctionFragment; - types_option_geo: FunctionFragment; - types_raw_slice: FunctionFragment; - types_result: FunctionFragment; - types_std_string: FunctionFragment; - types_str: FunctionFragment; - types_str_slice: FunctionFragment; - types_struct: FunctionFragment; - types_tuple: FunctionFragment; - types_tuple_with_native_types: FunctionFragment; - types_u16: FunctionFragment; - types_u256: FunctionFragment; - types_u32: FunctionFragment; - types_u64: FunctionFragment; - types_u8: FunctionFragment; - types_value_then_empty: FunctionFragment; - types_value_then_empty_then_value: FunctionFragment; - types_value_then_value_then_empty_then_empty: FunctionFragment; - types_vector_geo: FunctionFragment; - types_vector_option: FunctionFragment; - types_vector_u8: FunctionFragment; + alias_types_tuple_with_native_types: AbiCoderFunction; + type_external_enum: AbiCoderFunction; + type_external_struct: AbiCoderFunction; + type_identity: AbiCoderFunction; + types_address: AbiCoderFunction; + types_array: AbiCoderFunction; + types_asset_id: AbiCoderFunction; + types_b256: AbiCoderFunction; + types_b512: AbiCoderFunction; + types_bool: AbiCoderFunction; + types_bytes: AbiCoderFunction; + types_contract_id: AbiCoderFunction; + types_empty: AbiCoderFunction; + types_empty_then_value: AbiCoderFunction; + types_enum: AbiCoderFunction; + types_enum_with_vector: AbiCoderFunction; + types_evm_address: AbiCoderFunction; + types_generic_enum: AbiCoderFunction; + types_generic_struct: AbiCoderFunction; + types_option: AbiCoderFunction; + types_option_struct: AbiCoderFunction; + types_raw_slice: AbiCoderFunction; + types_result: AbiCoderFunction; + types_std_string: AbiCoderFunction; + types_str: AbiCoderFunction; + types_str_slice: AbiCoderFunction; + types_struct: AbiCoderFunction; + types_tuple: AbiCoderFunction; + types_tuple_with_native_types: AbiCoderFunction; + types_u16: AbiCoderFunction; + types_u256: AbiCoderFunction; + types_u32: AbiCoderFunction; + types_u64: AbiCoderFunction; + types_u8: AbiCoderFunction; + types_value_then_empty: AbiCoderFunction; + types_value_then_empty_then_value: AbiCoderFunction; + types_value_then_value_then_empty_then_empty: AbiCoderFunction; + types_vector_geo: AbiCoderFunction; + types_vector_option: AbiCoderFunction; + types_vector_u8: AbiCoderFunction; }; } @@ -1203,20 +1200,20 @@ export class MyContract extends Contract { static readonly abi = abi; static readonly storageSlots = storageSlots; - declare interface: MyContractInterface; + declare interface: MyContractAbiCoder; declare functions: { alias_types_tuple_with_native_types: InvokeFunction<[x: [AssetIdInput, AssetIdInput, boolean]], [AssetIdOutput, AssetIdOutput, boolean]>; - type_address: InvokeFunction<[x: AddressInput], AddressOutput>; - type_contract_id: InvokeFunction<[x: ContractIdInput], ContractIdOutput>; type_external_enum: InvokeFunction<[x: ExternalEnumInput], ExternalEnumOutput>; type_external_struct: InvokeFunction<[x: ExternalStructInput], ExternalStructOutput>; type_identity: InvokeFunction<[x: IdentityInput], IdentityOutput>; + types_address: InvokeFunction<[x: AddressInput], AddressOutput>; types_array: InvokeFunction<[x: [BigNumberish, BigNumberish, BigNumberish]], [number, number, number]>; types_asset_id: InvokeFunction<[x: AssetIdInput], AssetIdOutput>; types_b256: InvokeFunction<[x: string], string>; types_b512: InvokeFunction<[x: string], string>; types_bool: InvokeFunction<[x: boolean], boolean>; types_bytes: InvokeFunction<[x: Bytes], Bytes>; + types_contract_id: InvokeFunction<[x: ContractIdInput], ContractIdOutput>; types_empty: InvokeFunction<[x?: undefined], void>; types_empty_then_value: InvokeFunction<[x: undefined, y: BigNumberish], void>; types_enum: InvokeFunction<[x: MyEnumInput], MyEnumOutput>; @@ -1225,7 +1222,7 @@ export class MyContract extends Contract { types_generic_enum: InvokeFunction<[x: GenericEnumInput], GenericEnumOutput>; types_generic_struct: InvokeFunction<[x: GenericStructWithEnumInput], GenericStructWithEnumOutput>; types_option: InvokeFunction<[x?: Option], Option>; - types_option_geo: InvokeFunction<[x?: Option], Option>; + types_option_struct: InvokeFunction<[x?: Option], Option>; types_raw_slice: InvokeFunction<[x: RawSlice], RawSlice>; types_result: InvokeFunction<[x: Result], Result>; types_std_string: InvokeFunction<[x: StdString], StdString>; diff --git a/packages/abi-typegen/test/fixtures/templates/predicate-with-configurable/main.hbs b/packages/abi-typegen/test/fixtures/templates/predicate-with-configurable/main.hbs index fdacb6def91..27b40fec9ec 100644 --- a/packages/abi-typegen/test/fixtures/templates/predicate-with-configurable/main.hbs +++ b/packages/abi-typegen/test/fixtures/templates/predicate-with-configurable/main.hbs @@ -11,6 +11,7 @@ */ import { + AbiSpecification, BigNumberish, decompressBytecode, InputValue, @@ -31,7 +32,7 @@ export type MyPredicateParameters = Omit< 'abi' | 'bytecode' >; -const abi = { +const abi: AbiSpecification = { "programType": "predicate", "specVersion": "1", "encodingVersion": "1", diff --git a/packages/abi-typegen/test/fixtures/templates/predicate/main.hbs b/packages/abi-typegen/test/fixtures/templates/predicate/main.hbs index 8eaed3fbf41..fe83ed8aba1 100644 --- a/packages/abi-typegen/test/fixtures/templates/predicate/main.hbs +++ b/packages/abi-typegen/test/fixtures/templates/predicate/main.hbs @@ -11,6 +11,7 @@ */ import { + AbiSpecification, BigNumberish, BN, decompressBytecode, @@ -39,7 +40,7 @@ export type MyPredicateParameters = Omit< 'abi' | 'bytecode' >; -const abi = { +const abi: AbiSpecification = { "programType": "predicate", "specVersion": "1", "encodingVersion": "1", diff --git a/packages/abi-typegen/test/fixtures/templates/script-with-configurable/main.hbs b/packages/abi-typegen/test/fixtures/templates/script-with-configurable/main.hbs index 71b9ba3cb53..7753b5dfdf2 100644 --- a/packages/abi-typegen/test/fixtures/templates/script-with-configurable/main.hbs +++ b/packages/abi-typegen/test/fixtures/templates/script-with-configurable/main.hbs @@ -11,6 +11,7 @@ */ import { + AbiSpecification, Account, BigNumberish, decompressBytecode, @@ -27,7 +28,7 @@ export type MyScriptConfigurables = Partial<{ SHOULD_RETURN: boolean; }>; -const abi = { +const abi: AbiSpecification = { "programType": "script", "specVersion": "1", "encodingVersion": "1", diff --git a/packages/abi-typegen/test/fixtures/templates/script/main.hbs b/packages/abi-typegen/test/fixtures/templates/script/main.hbs index aa93e951140..0d08fdbfabd 100644 --- a/packages/abi-typegen/test/fixtures/templates/script/main.hbs +++ b/packages/abi-typegen/test/fixtures/templates/script/main.hbs @@ -11,6 +11,7 @@ */ import { + AbiSpecification, Account, BigNumberish, BN, @@ -31,7 +32,7 @@ export type ScoreOutput = { user: number, points: number }; export type MyScriptInputs = [vec: Vec, enm: MyGenericEnumInput, opt: Option, res: Result, BigNumberish>]; export type MyScriptOutput = boolean; -const abi = { +const abi: AbiSpecification = { "programType": "script", "specVersion": "1", "encodingVersion": "1", diff --git a/packages/abi/package.json b/packages/abi/package.json index 0fd951ba435..7c821fb7093 100644 --- a/packages/abi/package.json +++ b/packages/abi/package.json @@ -26,8 +26,13 @@ "postbuild": "tsx ../../scripts/postbuild.ts" }, "dependencies": { + "@fuel-ts/crypto": "workspace:*", "@fuel-ts/errors": "workspace:*", - "@fuel-ts/utils": "workspace:*" + "@fuel-ts/hasher": "workspace:*", + "@fuel-ts/interfaces": "workspace:*", + "@fuel-ts/math": "workspace:*", + "@fuel-ts/utils": "workspace:*", + "type-fest": "^4.26.1" }, "devDependencies": {} } diff --git a/packages/abi/src/coder/abi-coder-types.ts b/packages/abi/src/coder/abi-coder-types.ts index 10051c76805..0cdbacc6d1f 100644 --- a/packages/abi/src/coder/abi-coder-types.ts +++ b/packages/abi/src/coder/abi-coder-types.ts @@ -1 +1,85 @@ -// Placeholder +import type { BytesLike } from '@fuel-ts/interfaces'; +import type { BN, BNInput } from '@fuel-ts/math'; + +import type { AbiConfigurable, AbiFunction, AbiLoggedType, AbiType } from '../parser'; + +export type Primitive = string | number | boolean; + +export type Option = T | undefined; + +/** + * The type of value you can provide to `Coder.encode` + */ +export type InputValue = + | Primitive + | BN + | Option + | BytesLike + | InputValue[] + | { [key: string]: InputValue } + | Record; + +/** + * The type of value you can get from `Coder.decode` + */ +export type DecodedValue = + | Primitive + | DecodedValue[] + | { [key: string]: DecodedValue } + | Record; + +export type TypesOfCoder = + TCoder extends Coder ? { Input: TInput; Decoded: TDecoded } : never; + +export interface Coder { + type: string; + encode: (value: TEncoded) => Uint8Array; + decode: (value: Uint8Array) => TDecoded; + encodedLength: (data: Uint8Array) => number; +} + +export abstract class AbstractCoder + implements Coder +{ + abstract readonly type: string; + abstract encodedLength: (data: Uint8Array) => number; + abstract encode(value: TEncoded): Uint8Array; + abstract decode(data: Uint8Array): TDecoded; +} + +export type GetCoderParams = { name?: string; type: AbiType }; +export type GetCoderFn = (params: GetCoderParams) => Coder; + +export interface AbiCoderFunction { + name: AbiFunction['name']; + inputs: AbiFunction['inputs']; + signature: string; + selector: string; + selectorBytes: Uint8Array; + attributes: AbiFunction['attributes']; + + // Methods + isReadOnly: () => boolean; + encodeArguments: (values: InputValue[]) => Uint8Array; + decodeArguments: (data: BytesLike) => DecodedValue[]; + encodeOutput: (value: InputValue) => Uint8Array; + decodeOutput: (data: BytesLike) => DecodedValue; +} + +export interface AbiCoderConfigurable { + name: AbiConfigurable['name']; + offset: AbiConfigurable['offset']; + encode: (values: InputValue) => Uint8Array; + decode: (data: BytesLike) => DecodedValue; +} + +export interface AbiCoderLog { + logId: AbiLoggedType['logId']; + encode: (values: InputValue) => Uint8Array; + decode: (data: BytesLike) => DecodedValue; +} + +export interface AbiCoderType { + encode: (value: InputValue) => Uint8Array; + decode: (data: BytesLike) => DecodedValue; +} diff --git a/packages/abi/src/coder/abi-coder.ts b/packages/abi/src/coder/abi-coder.ts index 562990d4048..0f941db8853 100644 --- a/packages/abi/src/coder/abi-coder.ts +++ b/packages/abi/src/coder/abi-coder.ts @@ -1,10 +1,103 @@ -import type { Abi } from '../parser'; -import type { AbiSpecification } from '../parser/abi-parser'; -import { AbiParser } from '../parser/abi-parser'; +import { FuelError } from '@fuel-ts/errors'; + +import type { Abi, AbiSpecification } from '../parser'; +import { AbiParser } from '../parser'; + +import type { + AbiCoderConfigurable, + AbiCoderFunction, + AbiCoderLog, + AbiCoderType, +} from './abi-coder-types'; +import { AbiEncoding } from './encoding/encoding'; +import { makeConfigurable } from './utils/createConfigurable'; +import { makeFunction } from './utils/createFunction'; +import { makeLog } from './utils/createLog'; +import { makeType } from './utils/createType'; export class AbiCoder { - private abi: Abi; - constructor(parsableAbi: AbiSpecification) { - this.abi = AbiParser.parse(parsableAbi); + // Internal properties + public readonly abi: Abi; + public readonly specification: AbiSpecification; + private encoding: AbiEncoding; + + // Exposed properties + public readonly functions: Record; + public readonly configurables: Record; + public readonly logs: Record; + + public constructor(specification: AbiSpecification) { + this.abi = AbiParser.parse(specification); + this.specification = specification; + this.encoding = AbiEncoding.from(this.abi.encodingVersion); + + const { functions, configurables, loggedTypes } = this.abi; + this.functions = Object.fromEntries( + functions.map((fn) => [fn.name, makeFunction(fn, this.encoding)]) + ); + this.configurables = Object.fromEntries( + configurables.map((conf) => [conf.name, makeConfigurable(conf, this.encoding)]) + ); + this.logs = Object.fromEntries( + loggedTypes.map((log) => [log.logId, makeLog(log, this.encoding)]) + ); + } + + static fromAbi(abi: AbiSpecification): AbiCoder { + return new AbiCoder(abi); + } + + public getFunction(nameOrSignatureOrSelector: string): AbiCoderFunction { + const fn = Object.values(this.functions).find( + (f) => + f.name === nameOrSignatureOrSelector || + f.signature === nameOrSignatureOrSelector || + f.selector === nameOrSignatureOrSelector + ); + + if (fn === undefined) { + throw new FuelError( + FuelError.CODES.FUNCTION_NOT_FOUND, + `Unable to find function with the name or signature or selector of "${nameOrSignatureOrSelector}".` + ); + } + + return fn; + } + + public getConfigurable(name: string): AbiCoderConfigurable { + const configurable = this.configurables[name]; + if (configurable === undefined) { + throw new FuelError( + FuelError.CODES.CONFIGURABLE_NOT_FOUND, + `Configurable with name '${name}' doesn't exist in the ABI.` + ); + } + + return configurable; + } + + public getLog(logId: string): AbiCoderLog { + const log = this.logs[logId]; + if (log === undefined) { + throw new FuelError( + FuelError.CODES.LOG_TYPE_NOT_FOUND, + `Log type with logId '${logId}' doesn't exist in the ABI.` + ); + } + + return log; + } + + public getType(concreteTypeId: string): AbiCoderType { + const type = this.abi.types.find((t) => t.concreteTypeId === concreteTypeId); + if (type === undefined) { + throw new FuelError( + FuelError.CODES.TYPE_NOT_FOUND, + `Type with concreteTypeId '${concreteTypeId}' doesn't exist in the ABI.` + ); + } + + return makeType(type, this.encoding); } } diff --git a/packages/abi/src/coder/constants.ts b/packages/abi/src/coder/constants.ts new file mode 100644 index 00000000000..9017fe592c2 --- /dev/null +++ b/packages/abi/src/coder/constants.ts @@ -0,0 +1,69 @@ +/** + * Property space and config constants + */ +export const WORD_SIZE = 8; +export const BYTES_32 = 32; +export const UTXO_ID_LEN = BYTES_32 + 2; +export const ASSET_ID_LEN = BYTES_32; +export const CONTRACT_ID_LEN = BYTES_32; + +export const calculateVmTxMemory = ({ maxInputs }: { maxInputs: number }) => + BYTES_32 + // Tx ID + ASSET_ID_LEN + // Base asset ID + // Asset ID/Balance coin input pairs + maxInputs * (ASSET_ID_LEN + WORD_SIZE) + + WORD_SIZE; // Tx size + +// SCRIPT_FIXED_SIZE = 104 +export const SCRIPT_FIXED_SIZE = + WORD_SIZE + // Identifier + WORD_SIZE + // Gas limit + WORD_SIZE + // Script size + WORD_SIZE + // Script data size + WORD_SIZE + // Policies + WORD_SIZE + // Inputs size + WORD_SIZE + // Outputs size + WORD_SIZE + // Witnesses size + BYTES_32; // Receipts root + +// @deprecated unused constant +const MAX_INPUTS = 255; +// @deprecated unused constant +const ADDRESS_LEN = BYTES_32; +// @deprecated unused constant +const NONCE_LEN = BYTES_32; +// @deprecated unused constant +const TX_LEN = WORD_SIZE * 4; +// @deprecated unused constant +const TX_POINTER_LEN = WORD_SIZE * 2; +// @deprecated unused constant +const MAX_BYTES = 2 ** 32 - 1; // Max u32 + +// INPUT_COIN_FIXED_SIZE = 176 +// @deprecated unused constant +const INPUT_COIN_FIXED_SIZE = + WORD_SIZE + // Identifier + TX_LEN + // Utxo Length + WORD_SIZE + // Output Index + ADDRESS_LEN + // Owner + WORD_SIZE + // Amount + ASSET_ID_LEN + // Asset id + TX_POINTER_LEN + // TxPointer + WORD_SIZE + // Witnesses index + WORD_SIZE + // Predicate size + WORD_SIZE + // Predicate data size + WORD_SIZE; // Predicate gas used + +// INPUT_MESSAGE_FIXED_SIZE = 168 +// @deprecated unused constant +export const INPUT_MESSAGE_FIXED_SIZE = + WORD_SIZE + // Identifier + ADDRESS_LEN + // Sender + ADDRESS_LEN + // Recipient + WORD_SIZE + // Amount + NONCE_LEN + // Nonce + WORD_SIZE + // witness_index + WORD_SIZE + // Data size + WORD_SIZE + // Predicate size + WORD_SIZE + // Predicate data size + WORD_SIZE; // Predicate gas used diff --git a/packages/abi/src/coder/encoding/encoding.ts b/packages/abi/src/coder/encoding/encoding.ts new file mode 100644 index 00000000000..27bfb24f33f --- /dev/null +++ b/packages/abi/src/coder/encoding/encoding.ts @@ -0,0 +1,43 @@ +import { type Matcher } from '../../matchers/sway-type-matchers'; +import type { Coder, GetCoderFn, GetCoderParams } from '../abi-coder-types'; + +import { createCoderMatcher, type SupportedCoder, type SupportedCoders } from './matching'; +import { v1 } from './v1/v1'; + +export class AbiEncoding { + private supportedEncodings: Record = { + '1': v1, + }; + + public coders: SupportedCoders; + private matcher: Matcher; + + private constructor(version: string) { + const coders = this.supportedEncodings[version as keyof typeof this.supportedEncodings]; + this.coders = coders; + this.matcher = createCoderMatcher(this.coders); + } + + static from(version: string): AbiEncoding { + return new AbiEncoding(version); + } + + static v1 = this.from('1'); + + public getCoder: GetCoderFn = (opts: GetCoderParams): Coder => { + const coder = this.matcher(opts.type); + if (!coder) { + throw new Error(`Unsupported coder type "${opts.type.swayType}" for element "${opts.name}"`); + } + + if (typeof coder === 'object') { + return coder as Coder; + } + + if (typeof coder === 'function') { + return coder.fromAbi(opts, this.getCoder) as Coder; + } + + return coder; + }; +} diff --git a/packages/abi/src/coder/encoding/index.ts b/packages/abi/src/coder/encoding/index.ts new file mode 100644 index 00000000000..30f6d97d16b --- /dev/null +++ b/packages/abi/src/coder/encoding/index.ts @@ -0,0 +1 @@ +export { AbiEncoding } from './encoding'; diff --git a/packages/abi/src/coder/encoding/matching.ts b/packages/abi/src/coder/encoding/matching.ts new file mode 100644 index 00000000000..8f11f2cf941 --- /dev/null +++ b/packages/abi/src/coder/encoding/matching.ts @@ -0,0 +1,49 @@ +import { createMatcher } from '../../matchers/sway-type-matchers'; + +import type { SupportedCodersV1 } from './v1/v1.types'; + +export type SupportedCoders = SupportedCodersV1; +export type SupportedCoder = SupportedCoders[keyof SupportedCoders]; + +export const createCoderMatcher = (coders: SupportedCoders) => + createMatcher({ + u8: coders.u8, + u16: coders.u16, + u32: coders.u32, + u64: coders.u64, + rawUntypedPtr: coders.u64, + u256: coders.u256, + rawUntypedSlice: coders.rawSlice, + b256: coders.b256, + b512: coders.b512, + bool: coders.bool, + void: coders.void, + + string: coders.string, + array: coders.array, + tuple: coders.tuple, + vector: coders.vector, + struct: coders.struct, + bytes: coders.byte, + stdString: coders.stdString, + str: coders.str, + enum: coders.enum, + option: coders.option, + + // Unmatchable + generic: { + type: 'generic', + encodedLength: () => { + throw new Error('A generic type should not be matched'); + }, + encode: () => { + throw new Error('A generic type should not be matched'); + }, + decode: () => { + throw new Error('A generic type should not be matched'); + }, + }, + assetId: coders.struct, + evmAddress: coders.struct, + result: coders.enum, + }); diff --git a/packages/abi/src/coder/encoding/v1/array.ts b/packages/abi/src/coder/encoding/v1/array.ts new file mode 100644 index 00000000000..68fda9e6275 --- /dev/null +++ b/packages/abi/src/coder/encoding/v1/array.ts @@ -0,0 +1,73 @@ +import { concat } from '@fuel-ts/utils'; + +import { ARRAY_REGEX } from '../../../matchers/sway-type-matchers'; +import type { AbiTypeComponent } from '../../../parser'; +import type { + AbstractCoder, + Coder, + GetCoderFn, + GetCoderParams, + TypesOfCoder, +} from '../../abi-coder-types'; + +/** + * `array` coder + */ +type ArrayEncodeValue = Array['Input']>; +type ArrayDecodeValue = Array['Decoded']>; + +export const arrayCoder = (opts: { + coder: TCoder; + size: number; +}): Coder, ArrayDecodeValue> => { + const { coder, size } = opts; + return { + type: `array`, + encodedLength: (data: Uint8Array) => { + let elementLength = 0; + let currData = data; + for (let i = 0; i < size; i++) { + currData = data.slice(elementLength); + elementLength += coder.encodedLength(currData); + } + return elementLength; + }, + encode: (value: ArrayEncodeValue): Uint8Array => + concat(value.map((v) => coder.encode(v))), + decode: (data: Uint8Array): ArrayDecodeValue => { + let offset = 0; + let currData = data; + const decodedValue = Array(size) + .fill(0) + .map(() => { + currData = data.slice(offset, data.length); + const fieldLength = coder.encodedLength(currData); + const fieldData = currData.slice(0, fieldLength); + offset += fieldLength; + return coder.decode(fieldData); + }); + + return decodedValue; + }, + }; +}; + +arrayCoder.fromAbi = ({ type: { swayType, components } }: GetCoderParams, getCoder: GetCoderFn) => { + // @TODO change ARRAY_REGEX.length to size (size of the array seems better terminology) + const arrayMatch = ARRAY_REGEX.exec(swayType)?.groups; + if (!arrayMatch) { + throw new Error(`Unable to parse array length for "${swayType}".`); + } + + const arraySize = parseInt(arrayMatch.length, 10); + const arrayElement: AbiTypeComponent | undefined = components?.[0]; + if (!arrayElement) { + throw new Error(`The provided Array type is missing an item of 'component'.`); + } + + const arrayElementCoder = getCoder(arrayElement); + return arrayCoder({ + coder: arrayElementCoder, + size: arraySize, + }); +}; diff --git a/packages/abi/src/coder/encoding/v1/enum.ts b/packages/abi/src/coder/encoding/v1/enum.ts new file mode 100644 index 00000000000..9ab23fc4021 --- /dev/null +++ b/packages/abi/src/coder/encoding/v1/enum.ts @@ -0,0 +1,108 @@ +import { concat } from '@fuel-ts/utils'; +import type { RequireExactlyOne } from 'type-fest'; + +import type { AbiTypeComponent } from '../../../parser'; +import type { + AbstractCoder, + Coder, + GetCoderFn, + GetCoderParams, + TypesOfCoder, +} from '../../abi-coder-types'; + +import { u64 } from './fixed'; + +/** + * `enum` coder + */ +export type EnumEncodeValue> = RequireExactlyOne<{ + [P in keyof TCoders]: TypesOfCoder['Input']; +}>; +export type EnumDecodeValue> = RequireExactlyOne<{ + [P in keyof TCoders]: TypesOfCoder['Decoded']; +}>; + +export const CASE_KEY_WORD_LENGTH = 8; + +export const enumCoder = >(opts: { + coders: TCoders; + type?: string; +}) => { + const isNativeValue = (value: EnumEncodeValue) => typeof value === 'string'; + const isNativeCoder = (coder: Coder) => opts.type !== 'option' && coder.type === 'void'; + + return { + type: opts.type ?? 'enum', + encodedLength: (data: Uint8Array) => { + // Get the index for the case + const caseIndexBytes = data.slice(0, CASE_KEY_WORD_LENGTH); + const caseIndex = u64.decode(caseIndexBytes).toNumber(); + + // Get the coder for the case + const caseCoder = Object.values(opts.coders)[caseIndex]; + const caseValueBytes = data.slice(CASE_KEY_WORD_LENGTH); + const caseValueLength = caseCoder.encodedLength(caseValueBytes); + return CASE_KEY_WORD_LENGTH + caseValueLength; + }, + encode: (value: EnumEncodeValue): Uint8Array => { + if (isNativeValue(value)) { + if (!opts.coders[value]) { + throw new Error("Unable to encode native enum as coder can't be found"); + } + + const valueCoder = opts.coders[value]; + const encodedValue = valueCoder.encode([]); + const caseIndex = Object.keys(opts.coders).indexOf(value); + + // @TODO investigate issue with the EnumCoder. + // const padding = new Uint8Array(this.#encodedValueSize - valueCoder.encodedLength); + // return concat([u64.encode(caseIndex), padding, encodedValue]); + + return concat([u64.encode(caseIndex), encodedValue]); + } + + // Obtain the case key and index + const [caseKey] = Object.keys(value); + const caseIndex = Object.keys(opts.coders).indexOf(caseKey); + + // Encode the case value + const valueCoder = opts.coders[caseKey]; + const encodedValue = valueCoder.encode(value[caseKey]); + + return concat([u64.encode(caseIndex), encodedValue]); + }, + decode: (data: Uint8Array): EnumDecodeValue => { + // Decode the case index + const caseBytesLength = u64.encodedLength(data); + const caseBytes = data.slice(0, caseBytesLength); + const caseIndex = u64.decode(caseBytes).toNumber(); + const caseKey = Object.keys(opts.coders)[caseIndex]; + + // Decode the case value + const caseValueCoder = opts.coders[caseKey]; + const caseValueBytes = data.slice(caseBytesLength, data.length); + const caseValue = caseValueCoder.decode(caseValueBytes); + + if (isNativeCoder(caseValueCoder)) { + return caseKey as unknown as EnumDecodeValue; + } + + return { [caseKey]: caseValue } as EnumDecodeValue; + }, + }; +}; + +enumCoder.fromAbi = ({ type: { components } }: GetCoderParams, getCoder: GetCoderFn) => { + if (!components) { + throw new Error(`The provided Enum type is missing an item of 'components'.`); + } + + const coders = components.reduce((obj, component: AbiTypeComponent) => { + const o: Record = obj; + + o[component.name] = getCoder(component); + return o; + }, {}); + + return enumCoder({ coders }); +}; diff --git a/packages/abi/src/coder/encoding/v1/fixed.ts b/packages/abi/src/coder/encoding/v1/fixed.ts new file mode 100644 index 00000000000..0f9c98257e7 --- /dev/null +++ b/packages/abi/src/coder/encoding/v1/fixed.ts @@ -0,0 +1,52 @@ +import type { BN, BNInput } from '@fuel-ts/math'; +import { toNumber, toBytes, bn, toHex } from '@fuel-ts/math'; +import { arrayify } from '@fuel-ts/utils'; + +import type { Coder } from '../../abi-coder-types'; + +const createNumberCoder = (encodedLength: number, type: string): Coder => ({ + type, + encodedLength: () => encodedLength, + encode: (value: number): Uint8Array => toBytes(value, encodedLength), + decode: (data: Uint8Array): number => toNumber(data), +}); + +const createBigNumberCoder = (encodedLength: number, type: string): Coder => ({ + type, + encodedLength: () => encodedLength, + encode: (value: BN | BNInput): Uint8Array => toBytes(value, encodedLength), + decode: (data: Uint8Array): BN => bn(data), +}); + +const createHexCoder = (encodedLength: number, type: string): Coder => ({ + type, + encodedLength: () => encodedLength, + encode: (value: string): Uint8Array => arrayify(value), + decode: (data: Uint8Array): string => { + let bytes = data; + if (bn(data).isZero()) { + bytes = new Uint8Array(); + } + return toHex(bytes, encodedLength); + }, +}); + +export const u8: Coder = createNumberCoder(1, 'u8'); +export const u16: Coder = createNumberCoder(2, 'u16'); +export const u32: Coder = createNumberCoder(4, 'u32'); +export const u64: Coder = createBigNumberCoder(8, 'u64'); +export const u256: Coder = createBigNumberCoder(32, 'u256'); +export const b256: Coder = createHexCoder(32, 'b256'); +export const b512: Coder = createHexCoder(64, 'b512'); +export const voidCoder: Coder = { + type: 'void', + encodedLength: () => 0, + encode: (): Uint8Array => new Uint8Array(), + decode: (_data: Uint8Array): undefined => undefined, +}; +export const bool: Coder = { + type: 'bool', + encodedLength: u8.encodedLength, + encode: (value: boolean): Uint8Array => u8.encode(value ? 1 : 0), + decode: (data: Uint8Array): boolean => Boolean(u8.decode(data)).valueOf(), +}; diff --git a/packages/abi/src/coder/encoding/v1/heap.ts b/packages/abi/src/coder/encoding/v1/heap.ts new file mode 100644 index 00000000000..4fad7bca99b --- /dev/null +++ b/packages/abi/src/coder/encoding/v1/heap.ts @@ -0,0 +1,131 @@ +import { concat, toUtf8Bytes, toUtf8String } from '@fuel-ts/utils'; + +import type { AbstractCoder, Coder, GetCoderFn, GetCoderParams } from '../../abi-coder-types'; + +import { u64 } from './fixed'; + +export const DYNAMIC_WORD_LENGTH = 8; + +interface HeapCoder extends Omit, 'encodedLength' | 'decode'> { + encodedLength?(data: Uint8Array): number; + decode(data: Uint8Array, length: number): T; +} + +const createHeapType = (opts: HeapCoder): Coder => ({ + type: opts.type, + encodedLength: + opts.encodedLength ?? + ((data: Uint8Array) => { + const encodedLength = data.slice(0, DYNAMIC_WORD_LENGTH); + return u64.decode(encodedLength).toNumber() + DYNAMIC_WORD_LENGTH; + }), + encode: (value: T) => { + const encodedLength = u64.encode(value.length); + return concat([encodedLength, opts.encode(value)]); + }, + decode: (data: Uint8Array): T => { + const encodedLength = data.slice(0, DYNAMIC_WORD_LENGTH); + const length = u64.decode(encodedLength).toNumber(); + const payload = data.slice(DYNAMIC_WORD_LENGTH, data.length); + return opts.decode(payload, length); + }, +}); + +/** + * Byte + */ +export const byte = createHeapType({ + type: 'byte', + encode: (value: Uint8Array) => { + const bytes = value instanceof Uint8Array ? value : new Uint8Array(value); + return bytes; + }, + decode: (data: Uint8Array) => data, +}); + +/** + * Raw slice + */ +export const rawSlice = createHeapType({ + type: 'raw_slice', + encode: (value: number[]) => new Uint8Array([...value]), + decode: (data: Uint8Array) => [...data], +}); + +/** + * Std string + * + * This is the same as `str` + */ +export const stdString = createHeapType({ + type: 'std_string', + encode: (value: string) => toUtf8Bytes(value), + decode: (data: Uint8Array) => toUtf8String(data), +}); + +/** + * String slice + */ +export const str = createHeapType({ + type: 'str', + encode: (value: string) => toUtf8Bytes(value), + decode: (data: Uint8Array) => toUtf8String(data), +}); + +/** + * Vec + */ +type VecValue = ReturnType[]; + +export const vector = (opts: { + coder: TCoder; +}): Coder> => + createHeapType({ + type: 'vector', + encodedLength: (data: Uint8Array) => { + // Get the number of elements in the vector + const numberOfElementsBytes = data.slice(0, DYNAMIC_WORD_LENGTH); + const numberOfElements = u64.decode(numberOfElementsBytes).toNumber(); + + let elementLength = 0; + let currData = data.slice(DYNAMIC_WORD_LENGTH); + for (let i = 0; i < numberOfElements; i++) { + const encodedLength = opts.coder.encodedLength(currData); + currData = currData.slice(encodedLength); + elementLength += encodedLength; + } + + return DYNAMIC_WORD_LENGTH + elementLength; + }, + encode: (value: VecValue) => { + const encodedBytes = value.map((v) => opts.coder.encode(v)); + return concat(encodedBytes); + }, + decode: (data: Uint8Array, numberOfElements: number) => { + const elements = Array(numberOfElements); + + let currData = data; + for (let i = 0; i < numberOfElements; i++) { + const encodedLength = opts.coder.encodedLength(currData); + const elementData = currData.slice(0, encodedLength); + elements[i] = opts.coder.decode(elementData); + currData = currData.slice(encodedLength); + } + + return elements as VecValue; + }, + }); + +vector.fromAbi = ({ name, type: { components } }: GetCoderParams, getCoder: GetCoderFn) => { + if (!components) { + throw new Error(`The provided Vec type is missing an item of 'components'.`); + } + + const bufferComponent = components.find((component) => component.name === 'buf'); + if (!bufferComponent) { + throw new Error(`The Vec type provided is missing or has a malformed 'buf' component.`); + } + + const vecElementCoder = getCoder(bufferComponent); + return vector({ coder: vecElementCoder }); +}; diff --git a/packages/abi/src/coder/encoding/v1/index.ts b/packages/abi/src/coder/encoding/v1/index.ts deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/abi/src/coder/encoding/v1/option.ts b/packages/abi/src/coder/encoding/v1/option.ts new file mode 100644 index 00000000000..d0303122c5a --- /dev/null +++ b/packages/abi/src/coder/encoding/v1/option.ts @@ -0,0 +1,47 @@ +import type { AbiTypeComponent } from '../../../parser'; +import type { + AbstractCoder, + Coder, + GetCoderFn, + GetCoderParams, + Option, +} from '../../abi-coder-types'; + +import { enumCoder } from './enum'; +import type { EnumDecodeValue, EnumEncodeValue } from './enum'; + +type SwayOption = { None: [] } | { Some: T }; + +export const option = >(opts: { + coders: TCoders; +}) => { + const valueCoder = enumCoder({ ...opts, type: 'option' }); + return { + type: 'option', + encodedLength: (data: Uint8Array) => valueCoder.encodedLength(data), + encode: (value: Option): Uint8Array => { + const input: SwayOption = value !== undefined ? { Some: value } : { None: [] }; + return valueCoder.encode(input as unknown as EnumEncodeValue); + }, + decode: (data: Uint8Array): EnumDecodeValue[string] => { + const decoded = valueCoder.decode(data); + const optionValue = decoded && 'Some' in decoded ? decoded.Some : undefined; + return optionValue as EnumDecodeValue[string]; + }, + }; +}; + +option.fromAbi = ({ type: { components } }: GetCoderParams, getCoder: GetCoderFn) => { + if (!components) { + throw new Error(`The provided Enum type is missing an item of 'components'.`); + } + + const coders = components.reduce((obj, component: AbiTypeComponent) => { + const o: Record = obj; + + o[component.name] = getCoder(component); + return o; + }, {}); + + return option({ coders }); +}; diff --git a/packages/abi/src/coder/encoding/v1/string.ts b/packages/abi/src/coder/encoding/v1/string.ts new file mode 100644 index 00000000000..30a507106de --- /dev/null +++ b/packages/abi/src/coder/encoding/v1/string.ts @@ -0,0 +1,26 @@ +import { toUtf8Bytes, toUtf8String } from '@fuel-ts/utils'; + +import { STRING_REGEX } from '../../../matchers/sway-type-matchers'; +import type { Coder, GetCoderFn, GetCoderParams } from '../../abi-coder-types'; + +/** + * `string` coder + * + * @param encodedLength - The length of the encoded array. + * @returns + */ +export const string = ({ encodedLength }: { encodedLength: number }): Coder => ({ + type: 'string', + encodedLength: () => encodedLength, + encode: (value: string): Uint8Array => toUtf8Bytes(value), + decode: (data: Uint8Array): string => toUtf8String(data), +}); + +string.fromAbi = ({ type: { swayType } }: GetCoderParams, _getCoder: GetCoderFn) => { + const match = STRING_REGEX.exec(swayType)?.groups; + if (!match) { + throw new Error(`Unable to parse string length for "${swayType}".`); + } + const encodedLength = parseInt(match.length, 10); + return string({ encodedLength }); +}; diff --git a/packages/abi/src/coder/encoding/v1/struct.ts b/packages/abi/src/coder/encoding/v1/struct.ts new file mode 100644 index 00000000000..9fa0a627da9 --- /dev/null +++ b/packages/abi/src/coder/encoding/v1/struct.ts @@ -0,0 +1,67 @@ +import { concatBytes } from '@fuel-ts/utils'; + +import type { AbstractCoder, Coder, GetCoderFn, GetCoderParams } from '../../abi-coder-types'; + +/** + * `struct` coder + */ +type StructValue> = Record< + string, + ReturnType +>; + +export const struct = >(opts: { + coders: TCoders; +}): Coder> => ({ + type: 'struct', + encodedLength: (data: Uint8Array) => { + let offset = 0; + let currData = data; + + return Object.values(opts.coders).reduce((acc, coder) => { + currData = data.slice(offset, data.length); + const encodedLength = coder.encodedLength(currData); + offset += encodedLength; + return acc + encodedLength; + }, 0); + }, + encode: (value: StructValue): Uint8Array => { + const encodedValues = Object.entries(value).map(([key, val]) => { + const coder = opts.coders[key]; + if (!coder) { + throw new Error(`No coder found for field "${key}".`); + } + return coder.encode(val); + }); + return concatBytes(encodedValues); + }, + decode: (data: Uint8Array): StructValue => { + let offset = 0; + let currData = data; + + const decodedValue = Object.entries(opts.coders).reduce((acc, [caseKey, fieldCoder]) => { + currData = data.slice(offset, data.length); + const fieldLength = fieldCoder.encodedLength(currData); + const fieldData = currData.slice(0, fieldLength); + const decoded = fieldCoder.decode(fieldData) as StructValue[string]; + offset += fieldLength; + acc[caseKey as keyof StructValue] = decoded; + return acc; + }, {} as StructValue); + return decodedValue; + }, +}); + +struct.fromAbi = ({ type: { components } }: GetCoderParams, getCoder: GetCoderFn) => { + if (!components) { + throw new Error(`The provided Tuple type is missing an item of 'components'.`); + } + + const coders = components.reduce((obj, component) => { + const o: Record = obj; + + o[component.name] = getCoder(component); + return o; + }, {}); + return struct({ coders }); +}; diff --git a/packages/abi/src/coder/encoding/v1/tuple.ts b/packages/abi/src/coder/encoding/v1/tuple.ts new file mode 100644 index 00000000000..2aeefb4d631 --- /dev/null +++ b/packages/abi/src/coder/encoding/v1/tuple.ts @@ -0,0 +1,63 @@ +import { concatBytes } from '@fuel-ts/utils'; + +import type { AbiTypeComponent } from '../../../parser'; +import type { + AbstractCoder, + Coder, + GetCoderFn, + GetCoderParams, + TypesOfCoder, +} from '../../abi-coder-types'; + +/** + * Tuple coder + */ +export type TupleEncodeValue = { + [P in keyof TCoders]: TypesOfCoder['Input']; +}; +export type TupleDecodeValue = { + [P in keyof TCoders]: TypesOfCoder['Decoded']; +}; + +export const tuple = ({ + coders, +}: { + coders: TCoders; +}): Coder, TupleDecodeValue> => ({ + type: 'tuple', + encodedLength: (data: Uint8Array) => { + let offset = 0; + let currData = data; + + return coders.reduce((acc, coder) => { + currData = data.slice(offset, data.length); + const encodedLength = coder.encodedLength(currData); + offset += encodedLength; + return acc + encodedLength; + }, 0); + }, + encode: (value: TupleEncodeValue): Uint8Array => + concatBytes(coders.map((coder, i) => coder.encode(value[i]))), + decode: (data: Uint8Array): TupleDecodeValue => { + let offset = 0; + let currData = data; + + const decodedValue = coders.map((coder) => { + currData = data.slice(offset, data.length); + const fieldLength = coder.encodedLength(currData); + const fieldData = currData.slice(0, fieldLength); + offset += fieldLength; + return coder.decode(fieldData); + }); + return decodedValue as TupleDecodeValue; + }, +}); + +tuple.fromAbi = ({ type: { components } }: GetCoderParams, getCoder: GetCoderFn) => { + if (!components) { + throw new Error(`The provided Tuple type is missing an item of 'components'.`); + } + + const coders = components.map((component: AbiTypeComponent) => getCoder(component)); + return tuple({ coders }); +}; diff --git a/packages/abi/src/coder/encoding/v1/v1.ts b/packages/abi/src/coder/encoding/v1/v1.ts new file mode 100644 index 00000000000..10237d360ef --- /dev/null +++ b/packages/abi/src/coder/encoding/v1/v1.ts @@ -0,0 +1,32 @@ +import { arrayCoder } from './array'; +import { enumCoder } from './enum'; +import { voidCoder, u16, u32, u8, u64, u256, b256, b512, bool } from './fixed'; +import { byte, rawSlice, stdString, str, vector } from './heap'; +import { option } from './option'; +import { string } from './string'; +import { struct } from './struct'; +import { tuple } from './tuple'; +import type { SupportedCodersV1 } from './v1.types'; + +export const v1: SupportedCodersV1 = { + void: voidCoder, + u8, + u16, + u32, + u64, + u256, + bool, + b256, + b512, + string, + array: arrayCoder, + enum: enumCoder, + option, + struct, + tuple, + byte, + rawSlice, + str, + stdString, + vector, +}; diff --git a/packages/abi/src/coder/encoding/v1/v1.types.ts b/packages/abi/src/coder/encoding/v1/v1.types.ts new file mode 100644 index 00000000000..2728a1e40b8 --- /dev/null +++ b/packages/abi/src/coder/encoding/v1/v1.types.ts @@ -0,0 +1,33 @@ +import type { arrayCoder } from './array'; +import type { enumCoder } from './enum'; +import type { voidCoder, u16, u32, u8, u64, u256, b256, b512, bool } from './fixed'; +import type { byte, rawSlice, stdString, str, vector } from './heap'; +import type { option } from './option'; +import type { string } from './string'; +import type { struct } from './struct'; +import type { tuple } from './tuple'; + +export interface SupportedCodersV1 { + void: typeof voidCoder; + u8: typeof u8; + u16: typeof u16; + u32: typeof u32; + u64: typeof u64; + u256: typeof u256; + bool: typeof bool; + b256: typeof b256; + b512: typeof b512; + string: typeof string; + array: typeof arrayCoder; + enum: typeof enumCoder; + option: typeof option; + struct: typeof struct; + tuple: typeof tuple; + byte: typeof byte; + rawSlice: typeof rawSlice; + stdString: typeof stdString; + str: typeof str; + vector: typeof vector; +} + +export type CoderTypeV1 = SupportedCodersV1[keyof SupportedCodersV1]; diff --git a/packages/abi/src/coder/index.ts b/packages/abi/src/coder/index.ts index 242ad2e5a73..f1defb04095 100644 --- a/packages/abi/src/coder/index.ts +++ b/packages/abi/src/coder/index.ts @@ -1 +1,4 @@ export { AbiCoder } from './abi-coder'; +export * from './abi-coder-types'; +export * from './constants'; +export * from './encoding'; diff --git a/packages/abi/src/coder/utils/createConfigurable.ts b/packages/abi/src/coder/utils/createConfigurable.ts new file mode 100644 index 00000000000..b4fa49b39d5 --- /dev/null +++ b/packages/abi/src/coder/utils/createConfigurable.ts @@ -0,0 +1,23 @@ +import type { BytesLike } from '@fuel-ts/interfaces'; +import { arrayify } from '@fuel-ts/utils'; + +import type { AbiConfigurable } from '../../parser'; +import type { AbiCoderConfigurable, DecodedValue } from '../abi-coder-types'; +import type { AbiEncoding } from '../encoding/encoding'; + +export const makeConfigurable = ( + configurable: AbiConfigurable, + encoding: AbiEncoding +): AbiCoderConfigurable => { + const configurableCoder = encoding.getCoder(configurable); + return { + name: configurable.name, + offset: configurable.offset, + encode: configurableCoder.encode, + decode: (data: BytesLike): DecodedValue => { + const bytes = arrayify(data); + const encodedLength = configurableCoder.encodedLength(bytes); + return configurableCoder.decode(bytes.slice(0, encodedLength)) as DecodedValue; + }, + }; +}; diff --git a/packages/abi/src/coder/utils/createFunction.ts b/packages/abi/src/coder/utils/createFunction.ts new file mode 100644 index 00000000000..fa782a7e580 --- /dev/null +++ b/packages/abi/src/coder/utils/createFunction.ts @@ -0,0 +1,59 @@ +import { FuelError } from '@fuel-ts/errors'; +import type { BytesLike } from '@fuel-ts/interfaces'; +import { arrayify } from '@fuel-ts/utils'; + +import type { AbiFunction } from '../../parser'; +import type { AbiCoderFunction, DecodedValue, InputValue } from '../abi-coder-types'; +import type { AbiEncoding } from '../encoding/encoding'; + +import { createFunctionSelector } from './createFunctionSelector'; +import { createFunctionSignature } from './createFunctionSignature'; +import { getFunctionInputs } from './getFunctionInputs'; +import { padValuesWithUndefined } from './padValuesWithUndefined'; + +export const makeFunction = (fn: AbiFunction, encoding: AbiEncoding): AbiCoderFunction => { + const signature = createFunctionSignature(fn); + const argumentCoder = encoding.coders.tuple({ + coders: fn.inputs.map((input) => encoding.getCoder(input)), + }); + const outputCoder = encoding.getCoder({ type: fn.output }); + + const storageAttribute = fn.attributes?.find((attr) => attr.name === 'storage'); + const isReadOnly = !storageAttribute?.arguments?.includes('write'); + + const functionInputs = getFunctionInputs({ inputs: fn.inputs }); + const mandatoryInputLength = functionInputs.filter((i) => !i.isOptional).length; + + return { + // Function fields + name: fn.name, + inputs: fn.inputs, + signature, + selector: createFunctionSelector(signature), + selectorBytes: encoding.coders.stdString.encode(fn.name), + attributes: fn.attributes ?? [], + isReadOnly: () => isReadOnly, + + // Coders + encodeArguments: (values: InputValue[]): Uint8Array => { + if (values.length < mandatoryInputLength) { + throw new FuelError( + FuelError.CODES.ABI_TYPES_AND_VALUES_MISMATCH, + `Invalid number of arguments. Expected a minimum of ${mandatoryInputLength} arguments, received ${values.length}` + ); + } + + const paddedValues = padValuesWithUndefined(values, functionInputs); + return argumentCoder.encode(paddedValues); + }, + decodeArguments: (data: BytesLike): DecodedValue[] => { + const bytes = arrayify(data); + return argumentCoder.decode(bytes) as DecodedValue[]; + }, + encodeOutput: (value: InputValue): Uint8Array => outputCoder.encode(value), + decodeOutput: (data: BytesLike): DecodedValue => { + const bytes = arrayify(data); + return outputCoder.decode(bytes) as DecodedValue; + }, + }; +}; diff --git a/packages/abi/src/coder/utils/createFunctionSelector.ts b/packages/abi/src/coder/utils/createFunctionSelector.ts new file mode 100644 index 00000000000..faf890f08f5 --- /dev/null +++ b/packages/abi/src/coder/utils/createFunctionSelector.ts @@ -0,0 +1,9 @@ +import { bufferFromString } from '@fuel-ts/crypto'; +import { sha256 } from '@fuel-ts/hasher'; +import { bn } from '@fuel-ts/math'; + +export const createFunctionSelector = (functionSignature: string): string => { + const hashedFunctionSignature = sha256(bufferFromString(functionSignature, 'utf-8')); + // get first 4 bytes of signature + 0x prefix. then left-pad it to 8 bytes using toHex(8) + return bn(hashedFunctionSignature.slice(0, 10)).toHex(8); +}; diff --git a/packages/abi/src/coder/utils/createFunctionSignature.ts b/packages/abi/src/coder/utils/createFunctionSignature.ts new file mode 100644 index 00000000000..d352576e816 --- /dev/null +++ b/packages/abi/src/coder/utils/createFunctionSignature.ts @@ -0,0 +1,77 @@ +/* eslint-disable @typescript-eslint/no-use-before-define */ +import { + ARRAY_REGEX, + ENUM_REGEX, + STRING_REGEX, + STRUCT_REGEX, + swayTypeMatchers, +} from '../../matchers/sway-type-matchers'; +import type { AbiFunction, AbiType } from '../../parser'; + +const createSignaturePrefix = ({ type }: { type: AbiType }): string => { + switch (true) { + case STRUCT_REGEX.test(type.swayType): + return 's'; + + case ARRAY_REGEX.test(type.swayType): + return 'a'; + + case ENUM_REGEX.test(type.swayType): + return 'e'; + + default: + return ''; + } +}; + +const createSignatureContents = ({ type }: { type: AbiType }): string => { + const { swayType, components, metadata } = type; + + if (swayTypeMatchers.rawUntypedPtr(type)) { + return 'rawptr'; + } + + if (swayTypeMatchers.rawUntypedSlice(type)) { + return 'rawslice'; + } + + const strMatch = STRING_REGEX.exec(swayType)?.groups; + if (strMatch) { + return `str[${strMatch.length}]`; + } + + if (components === undefined) { + return swayType; + } + + const arrayMatch = ARRAY_REGEX.exec(swayType)?.groups; + if (arrayMatch) { + // TODO: sort out lint error + const arrayElementSignature = createSignatureForType(components[0]); + return `[${arrayElementSignature};${arrayMatch.length}]`; + } + + const typeArgumentsSignature = metadata?.typeArguments + ? `<${metadata.typeArguments + ?.map((typeArgument) => createSignatureForType({ type: typeArgument })) + .join(',')}>` + : ''; + + const componentsSignature = swayTypeMatchers.vector(type) + ? `s${typeArgumentsSignature}(rawptr,u64),u64` + : components.map(createSignatureForType).join(','); + + return `${typeArgumentsSignature}(${componentsSignature})`; +}; + +const createSignatureForType = (input: { type: AbiType }): string => { + const prefix = createSignaturePrefix(input); + const contents = createSignatureContents(input); + + return `${prefix}${contents}`; +}; + +export const createFunctionSignature = (fn: AbiFunction): string => { + const functionInputsSignature = fn.inputs.map(createSignatureForType).join(','); + return `${fn.name}(${functionInputsSignature})`; +}; diff --git a/packages/abi/src/coder/utils/createLog.ts b/packages/abi/src/coder/utils/createLog.ts new file mode 100644 index 00000000000..69b542374a8 --- /dev/null +++ b/packages/abi/src/coder/utils/createLog.ts @@ -0,0 +1,18 @@ +import type { BytesLike } from '@fuel-ts/interfaces'; +import { arrayify } from '@fuel-ts/utils'; + +import type { AbiLoggedType } from '../../parser'; +import type { AbiCoderLog, DecodedValue } from '../abi-coder-types'; +import type { AbiEncoding } from '../encoding/encoding'; + +export const makeLog = (loggedType: AbiLoggedType, encoding: AbiEncoding): AbiCoderLog => { + const loggedTypeCoder = encoding.getCoder(loggedType); + return { + logId: loggedType.logId, + encode: loggedTypeCoder.encode, + decode: (data: BytesLike): DecodedValue => { + const bytes = arrayify(data); + return loggedTypeCoder.decode(bytes) as DecodedValue; + }, + }; +}; diff --git a/packages/abi/src/coder/utils/createType.ts b/packages/abi/src/coder/utils/createType.ts new file mode 100644 index 00000000000..051cf92825c --- /dev/null +++ b/packages/abi/src/coder/utils/createType.ts @@ -0,0 +1,18 @@ +import type { BytesLike } from '@fuel-ts/interfaces'; +import { arrayify } from '@fuel-ts/utils'; + +import type { AbiType } from '../../parser'; +import type { AbiCoderType, DecodedValue } from '../abi-coder-types'; +import type { AbiEncoding } from '../encoding'; + +export function makeType(type: AbiType, encoding: AbiEncoding): AbiCoderType { + const coder = encoding.getCoder({ type }); + return { + encode: coder.encode, + decode: (data: BytesLike): DecodedValue => { + const bytes = arrayify(data); + const encodedLength = coder.encodedLength(bytes); + return coder.decode(bytes.slice(0, encodedLength)) as DecodedValue; + }, + }; +} diff --git a/packages/abi/src/coder/utils/getFunctionInputs.ts b/packages/abi/src/coder/utils/getFunctionInputs.ts new file mode 100644 index 00000000000..6d03c68b393 --- /dev/null +++ b/packages/abi/src/coder/utils/getFunctionInputs.ts @@ -0,0 +1,19 @@ +import { swayTypeMatchers } from '../../matchers/sway-type-matchers'; +import type { AbiFunctionInput } from '../../parser'; + +export type FunctionInput = TArg & { + isOptional: boolean; +}; + +export const getFunctionInputs = (params: { + inputs: readonly AbiFunctionInput[]; +}): Array => { + const { inputs } = params; + let isMandatory = false; + + return inputs.reduceRight((result, input) => { + isMandatory = + isMandatory || (!swayTypeMatchers.void(input.type) && !swayTypeMatchers.option(input.type)); + return [{ ...input, isOptional: !isMandatory }, ...result]; + }, [] as FunctionInput[]); +}; diff --git a/packages/abi/src/coder/utils/padValuesWithUndefined.ts b/packages/abi/src/coder/utils/padValuesWithUndefined.ts new file mode 100644 index 00000000000..c5cbe18a10f --- /dev/null +++ b/packages/abi/src/coder/utils/padValuesWithUndefined.ts @@ -0,0 +1,12 @@ +import type { InputValue } from '../abi-coder-types'; + +export const padValuesWithUndefined = (values: InputValue[], inputs: ArrayLike) => { + if (values.length >= inputs.length) { + return values; + } + + const paddedValues = values.slice(); + paddedValues.length = inputs.length; + paddedValues.fill(undefined, values.length); + return paddedValues; +}; diff --git a/packages/abi/src/index.ts b/packages/abi/src/index.ts index 22506bf91e0..c589c25a433 100644 --- a/packages/abi/src/index.ts +++ b/packages/abi/src/index.ts @@ -1,2 +1,3 @@ +export * from './parser'; export * from './coder'; export * from './gen'; diff --git a/packages/abi/src/matchers/sway-type-matchers.ts b/packages/abi/src/matchers/sway-type-matchers.ts index 9337b4cfb7e..f00f49b0ca5 100644 --- a/packages/abi/src/matchers/sway-type-matchers.ts +++ b/packages/abi/src/matchers/sway-type-matchers.ts @@ -27,50 +27,52 @@ export type SwayType = | 'rawUntypedPtr' | 'rawUntypedSlice'; -type Matcher = (type: string) => boolean; +export type Matcher = (opts: { swayType: string }) => T; -const voidMatcher: Matcher = (type) => type === '()'; -const bool: Matcher = (type) => type === 'bool'; -const u8: Matcher = (type) => type === 'u8'; -const u16: Matcher = (type) => type === 'u16'; -const u32: Matcher = (type) => type === 'u32'; -const u64: Matcher = (type) => type === 'u64'; -const u256: Matcher = (type) => type === 'u256'; -const b256: Matcher = (type) => type === 'b256'; +const voidMatcher: Matcher = ({ swayType }) => swayType === '()'; +const bool: Matcher = ({ swayType }) => swayType === 'bool'; +const u8: Matcher = ({ swayType }) => swayType === 'u8'; +const u16: Matcher = ({ swayType }) => swayType === 'u16'; +const u32: Matcher = ({ swayType }) => swayType === 'u32'; +const u64: Matcher = ({ swayType }) => swayType === 'u64'; +const u256: Matcher = ({ swayType }) => swayType === 'u256'; +const b256: Matcher = ({ swayType }) => swayType === 'b256'; export const GENERIC_REGEX = /^generic ([^\s]+)$/m; -const generic: Matcher = (type) => GENERIC_REGEX.test(type); +const generic: Matcher = ({ swayType }) => GENERIC_REGEX.test(swayType); export const STRING_REGEX = /^str\[(?[0-9]+)\]/; -const string: Matcher = (type) => STRING_REGEX.test(type); - -const str: Matcher = (type) => type === 'str'; +const string: Matcher = ({ swayType }) => STRING_REGEX.test(swayType); +const str: Matcher = ({ swayType }) => swayType === 'str'; export const TUPLE_REGEX = /^\((?.+)\)$/m; -const tuple: Matcher = (type) => TUPLE_REGEX.test(type); +const tuple: Matcher = ({ swayType }) => TUPLE_REGEX.test(swayType); export const ARRAY_REGEX = /^\[(?[\w\s\\[\]]+);\s*(?[0-9]+)\]/; -const array: Matcher = (type) => ARRAY_REGEX.test(type); +const array: Matcher = ({ swayType }) => ARRAY_REGEX.test(swayType); export const STRUCT_REGEX = /^struct (.+::)?(?.+)$/m; const STRUCT_STD_REGEX = /^struct std::.*(AssetId|B512|Vec|RawVec|EvmAddress|Bytes|String|RawBytes)$/m; -const struct: Matcher = (type) => STRUCT_REGEX.test(type) && !STRUCT_STD_REGEX.test(type); -const assetId: Matcher = (type) => type === 'struct std::asset_id::AssetId'; -const b512: Matcher = (type) => type === 'struct std::b512::B512'; -const bytes: Matcher = (type) => type === 'struct std::bytes::Bytes'; -const evmAddress: Matcher = (type) => type === 'struct std::vm::evm::evm_address::EvmAddress'; -const stdString: Matcher = (type) => type === 'struct std::string::String'; -const vector: Matcher = (type) => type === 'struct std::vec::Vec'; - -const option: Matcher = (type) => type === 'enum std::option::Option'; -const result: Matcher = (type) => type === 'enum std::result::Result'; +const struct: Matcher = ({ swayType }) => + STRUCT_REGEX.test(swayType) && !STRUCT_STD_REGEX.test(swayType); +const assetId: Matcher = ({ swayType }) => swayType === 'struct std::asset_id::AssetId'; +const b512: Matcher = ({ swayType }) => swayType === 'struct std::b512::B512'; +const bytes: Matcher = ({ swayType }) => swayType === 'struct std::bytes::Bytes'; +const evmAddress: Matcher = ({ swayType }) => + swayType === 'struct std::vm::evm::evm_address::EvmAddress'; +const stdString: Matcher = ({ swayType }) => swayType === 'struct std::string::String'; +const vector: Matcher = ({ swayType }) => swayType === 'struct std::vec::Vec'; + +const option: Matcher = ({ swayType }) => swayType === 'enum std::option::Option'; +const result: Matcher = ({ swayType }) => swayType === 'enum std::result::Result'; export const ENUM_REGEX = /^enum (.+::)?(?.+)$/m; -const enumMatcher: Matcher = (type) => !option(type) && !result(type) && ENUM_REGEX.test(type); +const enumMatcher: Matcher = (opts) => + !option(opts) && !result(opts) && ENUM_REGEX.test(opts.swayType); -const rawUntypedPtr: Matcher = (type) => type === 'raw untyped ptr'; -const rawUntypedSlice: Matcher = (type) => type === 'raw untyped slice'; +const rawUntypedPtr: Matcher = ({ swayType }) => swayType === 'raw untyped ptr'; +const rawUntypedSlice: Matcher = ({ swayType }) => swayType === 'raw untyped slice'; export const swayTypeMatchers: Record = { void: voidMatcher, @@ -82,9 +84,9 @@ export const swayTypeMatchers: Record = { u64, u256, b256, - str, string, + str, tuple, array, @@ -106,12 +108,12 @@ export const swayTypeMatchers: Record = { const swayTypeMatcherEntries = Object.entries(swayTypeMatchers); -export function createMatcher(mappings: Record) { +export function createMatcher(mappings: Record): Matcher { return (opts: { swayType: string }): T => { const { swayType } = opts; for (const [key, matcher] of swayTypeMatcherEntries) { - if (matcher(swayType)) { + if (matcher({ swayType })) { if (key in mappings) { return mappings[key as SwayType]; } diff --git a/packages/abi/src/parser/index.ts b/packages/abi/src/parser/index.ts index 1d0b7a7f86b..52984dcec05 100644 --- a/packages/abi/src/parser/index.ts +++ b/packages/abi/src/parser/index.ts @@ -1,3 +1,3 @@ -export { AbiParser } from './abi-parser'; +export { AbiParser, AbiSpecification } from './abi-parser'; export * from './abi'; export * from './specifications/v1/specification'; diff --git a/packages/abi/src/parser/specifications/v1/resolvable-type.ts b/packages/abi/src/parser/specifications/v1/resolvable-type.ts index 5b63fa80580..adb48f8f4d5 100644 --- a/packages/abi/src/parser/specifications/v1/resolvable-type.ts +++ b/packages/abi/src/parser/specifications/v1/resolvable-type.ts @@ -57,10 +57,13 @@ export class ResolvableType { * as it's then easier to reason about the vector * (you just treat is as a regular struct). */ - if (swayTypeMatchers.vector(metadataType.type)) { + if (swayTypeMatchers.vector({ swayType: metadataType.type })) { components = components?.map((c) => { if (c.name === 'buf') { - return c.typeArguments?.[0]; + return { + ...c.typeArguments?.[0], + name: c.name, + }; } return c; }) as AbiComponentV1[]; @@ -159,7 +162,7 @@ export class ResolvableType { mt: AbiMetadataTypeV1, typeArguments: AbiComponentV1['typeArguments'] ): ResolvableType | ResolvedType { - if (swayTypeMatchers.generic(mt.type)) { + if (swayTypeMatchers.generic({ swayType: mt.type })) { const resolvedTypeParameter = parent.typeParamsArgsMap?.find( ([tp]) => tp === mt.metadataTypeId )?.[1]; diff --git a/packages/abi/tsconfig.json b/packages/abi/tsconfig.json index b22c89a4b35..b0fced27d72 100644 --- a/packages/abi/tsconfig.json +++ b/packages/abi/tsconfig.json @@ -3,5 +3,5 @@ "compilerOptions": { "outDir": "./dist" }, - "include": ["src", "test"] + "include": ["src"] } diff --git a/packages/account/package.json b/packages/account/package.json index d1d78a1d269..844b9cd4c4e 100644 --- a/packages/account/package.json +++ b/packages/account/package.json @@ -48,7 +48,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@fuel-ts/abi-coder": "workspace:*", + "@fuel-ts/abi": "workspace:*", "@fuel-ts/address": "workspace:*", "@fuel-ts/crypto": "workspace:*", "@fuel-ts/errors": "workspace:*", diff --git a/packages/account/src/account.ts b/packages/account/src/account.ts index 112aaaaa318..38245bdabc9 100644 --- a/packages/account/src/account.ts +++ b/packages/account/src/account.ts @@ -1,4 +1,4 @@ -import { UTXO_ID_LEN } from '@fuel-ts/abi-coder'; +import { UTXO_ID_LEN } from '@fuel-ts/abi'; import { Address } from '@fuel-ts/address'; import { randomBytes } from '@fuel-ts/crypto'; import { ErrorCode, FuelError } from '@fuel-ts/errors'; diff --git a/packages/account/src/connectors/types/data-type.ts b/packages/account/src/connectors/types/data-type.ts index 04a63ad028d..06bf7a6ab4a 100644 --- a/packages/account/src/connectors/types/data-type.ts +++ b/packages/account/src/connectors/types/data-type.ts @@ -1,4 +1,4 @@ -import type { JsonAbi } from '@fuel-ts/abi-coder'; +import type { AbiSpecification } from '@fuel-ts/abi'; import type { RequireAtLeastOne } from 'type-fest'; /** @@ -41,4 +41,4 @@ export type SelectNetworkArguments = RequireAtLeastOne = { bytecode: BytesLike; provider: Provider; - abi: JsonAbi; + abi: AbiSpecification; data?: TData; configurableConstants?: TConfigurables; }; @@ -47,7 +47,7 @@ export class Predicate< > extends Account { bytes: Uint8Array; predicateData: TData = [] as unknown as TData; - interface: Interface; + interface: AbiCoder; /** * Creates an instance of the Predicate class. @@ -157,11 +157,11 @@ export class Predicate< */ private static processPredicateData( bytes: BytesLike, - jsonAbi: JsonAbi, + jsonAbi: AbiSpecification, configurableConstants?: { [name: string]: unknown } ) { let predicateBytes = arrayify(bytes); - const abiInterface: Interface = new Interface(jsonAbi); + const abiInterface: AbiCoder = AbiCoder.fromAbi(jsonAbi); if (abiInterface.functions.main === undefined) { throw new FuelError( @@ -232,7 +232,7 @@ export class Predicate< private static setConfigurableConstants( bytes: Uint8Array, configurableConstants: { [name: string]: unknown }, - abiInterface: Interface + abiInterface: AbiCoder ) { const mutatedBytes = bytes; @@ -254,7 +254,7 @@ export class Predicate< const { offset } = abiInterface.configurables[key]; - const encoded = abiInterface.encodeConfigurable(key, value as InputValue); + const encoded = abiInterface.getConfigurable(key).encode(value as InputValue); mutatedBytes.set(encoded, offset); }); @@ -319,7 +319,7 @@ export class Predicate< async deploy(account: Account) { return deployScriptOrPredicate({ deployer: account, - abi: this.interface.jsonAbi, + abi: this.interface.specification, bytecode: this.bytes, loaderInstanceCallback: (loaderBytecode, newAbi) => new Predicate({ diff --git a/packages/account/src/providers/transaction-request/input.ts b/packages/account/src/providers/transaction-request/input.ts index 3a3216058a4..ed8a73cc18d 100644 --- a/packages/account/src/providers/transaction-request/input.ts +++ b/packages/account/src/providers/transaction-request/input.ts @@ -1,4 +1,4 @@ -import { BYTES_32, UTXO_ID_LEN } from '@fuel-ts/abi-coder'; +import { BYTES_32, UTXO_ID_LEN } from '@fuel-ts/abi'; import { ZeroBytes32 } from '@fuel-ts/address/configs'; import { ErrorCode, FuelError } from '@fuel-ts/errors'; import type { BytesLike } from '@fuel-ts/interfaces'; diff --git a/packages/account/src/providers/transaction-request/script-transaction-request.ts b/packages/account/src/providers/transaction-request/script-transaction-request.ts index 07dc474cc4b..84ed9bb376d 100644 --- a/packages/account/src/providers/transaction-request/script-transaction-request.ts +++ b/packages/account/src/providers/transaction-request/script-transaction-request.ts @@ -1,5 +1,5 @@ -import type { InputValue, JsonAbi } from '@fuel-ts/abi-coder'; -import { Interface } from '@fuel-ts/abi-coder'; +import type { InputValue, AbiSpecification } from '@fuel-ts/abi'; +import { AbiCoder } from '@fuel-ts/abi'; import { addressify } from '@fuel-ts/address'; import { ZeroBytes32 } from '@fuel-ts/address/configs'; import type { AbstractScriptRequest, ContractIdLike, BytesLike } from '@fuel-ts/interfaces'; @@ -225,8 +225,8 @@ export class ScriptTransactionRequest extends BaseTransactionRequest { * @param args - The input arguments. * @returns The current instance of the `ScriptTransactionRequest`. */ - setData(abi: JsonAbi, args: InputValue[]): ScriptTransactionRequest { - const abiInterface = new Interface(abi); + setData(abi: AbiSpecification, args: InputValue[]): ScriptTransactionRequest { + const abiInterface = AbiCoder.fromAbi(abi); this.scriptData = abiInterface.functions.main.encodeArguments(args); return this; } diff --git a/packages/account/src/providers/transaction-request/transaction-request.ts b/packages/account/src/providers/transaction-request/transaction-request.ts index 9acc50532af..f492798834f 100644 --- a/packages/account/src/providers/transaction-request/transaction-request.ts +++ b/packages/account/src/providers/transaction-request/transaction-request.ts @@ -1,4 +1,4 @@ -import { UTXO_ID_LEN } from '@fuel-ts/abi-coder'; +import { UTXO_ID_LEN } from '@fuel-ts/abi'; import { Address, addressify } from '@fuel-ts/address'; import { ZeroBytes32 } from '@fuel-ts/address/configs'; import { randomBytes } from '@fuel-ts/crypto'; diff --git a/packages/account/src/providers/transaction-request/types.ts b/packages/account/src/providers/transaction-request/types.ts index c921cffab13..cdb63885b78 100644 --- a/packages/account/src/providers/transaction-request/types.ts +++ b/packages/account/src/providers/transaction-request/types.ts @@ -1,4 +1,4 @@ -import type { JsonAbi } from '@fuel-ts/abi-coder'; +import type { AbiSpecification } from '@fuel-ts/abi'; import type { TransactionType } from '@fuel-ts/transactions'; import type { @@ -36,6 +36,6 @@ export type TransactionRequestLike = | ({ type: TransactionType.Upload } & UploadTransactionRequestLike); export type JsonAbisFromAllCalls = { - main: JsonAbi; - otherContractsAbis: Record; + main: AbiSpecification; + otherContractsAbis: Record; }; diff --git a/packages/account/src/providers/transaction-response/getDecodedLogs.ts b/packages/account/src/providers/transaction-response/getDecodedLogs.ts index bbea779d335..756f00d020e 100644 --- a/packages/account/src/providers/transaction-response/getDecodedLogs.ts +++ b/packages/account/src/providers/transaction-response/getDecodedLogs.ts @@ -1,5 +1,5 @@ -import type { JsonAbi } from '@fuel-ts/abi-coder'; -import { Interface, BigNumberCoder } from '@fuel-ts/abi-coder'; +import type { AbiSpecification } from '@fuel-ts/abi'; +import { AbiCoder, AbiEncoding } from '@fuel-ts/abi'; import { ReceiptType } from '@fuel-ts/transactions'; import type { TransactionResultReceipt } from './transaction-response'; @@ -7,8 +7,8 @@ import type { TransactionResultReceipt } from './transaction-response'; /** @hidden */ export function getDecodedLogs( receipts: Array, - mainAbi: JsonAbi, - externalAbis: Record = {} + mainAbi: AbiSpecification, + externalAbis: Record = {} ): T[] { /** * This helper decodes logs from transaction receipts. @@ -28,14 +28,15 @@ export function getDecodedLogs( */ return receipts.reduce((logs: T[], receipt) => { if (receipt.type === ReceiptType.LogData || receipt.type === ReceiptType.Log) { - const interfaceToUse = new Interface(externalAbis[receipt.id] || mainAbi); + const interfaceToUse = AbiCoder.fromAbi(externalAbis[receipt.id] || mainAbi); const data = receipt.type === ReceiptType.Log - ? new BigNumberCoder('u64').encode(receipt.val0) + ? AbiEncoding.v1.coders.u64.encode(receipt.val0) : receipt.data; - const [decodedLog] = interfaceToUse.decodeLog(data, receipt.val1.toString()); + const log = interfaceToUse.getLog(receipt.val1.toString()); + const decodedLog = log.decode(data) as T; logs.push(decodedLog); } diff --git a/packages/account/src/providers/transaction-summary/call.ts b/packages/account/src/providers/transaction-summary/call.ts index e4b56da2320..56712710bd1 100644 --- a/packages/account/src/providers/transaction-summary/call.ts +++ b/packages/account/src/providers/transaction-summary/call.ts @@ -1,9 +1,10 @@ -import { Interface, type JsonAbi } from '@fuel-ts/abi-coder'; +import { AbiCoder } from '@fuel-ts/abi'; +import type { AbiSpecification } from '@fuel-ts/abi'; import type { BN } from '@fuel-ts/math'; import type { ReceiptCall } from '@fuel-ts/transactions'; type GetFunctionCallProps = { - abi: JsonAbi; + abi: AbiSpecification; receipt: ReceiptCall; rawPayload?: string; maxInputs: BN; @@ -18,10 +19,10 @@ export interface FunctionCall { } export const getFunctionCall = ({ abi, receipt }: GetFunctionCallProps): FunctionCall => { - const abiInterface = new Interface(abi); + const abiInterface = AbiCoder.fromAbi(abi); const callFunctionSelector = receipt.param1.toHex(8); const functionFragment = abiInterface.getFunction(callFunctionSelector); - const inputs = functionFragment.jsonFn.inputs; + const inputs = functionFragment.inputs; const encodedArgs = receipt.param2.toHex(); let argumentsProvided; diff --git a/packages/account/src/providers/transaction-summary/types.ts b/packages/account/src/providers/transaction-summary/types.ts index e8913be7e9a..f8a096b1464 100644 --- a/packages/account/src/providers/transaction-summary/types.ts +++ b/packages/account/src/providers/transaction-summary/types.ts @@ -1,4 +1,4 @@ -import type { JsonAbi } from '@fuel-ts/abi-coder'; +import type { AbiSpecification } from '@fuel-ts/abi'; import type { B256Address } from '@fuel-ts/interfaces'; import type { BN, BNInput } from '@fuel-ts/math'; import type { Input, Output, Transaction, TransactionType } from '@fuel-ts/transactions'; @@ -174,7 +174,7 @@ export type ReceiptParam = { receipts: TransactionResultReceipt[]; }; -export type AbiMap = Record; +export type AbiMap = Record; export type RawPayloadParam = { rawPayload?: string; diff --git a/packages/account/src/test-utils/launchNode.ts b/packages/account/src/test-utils/launchNode.ts index 9a5a6f61ddc..60b4dc4a1d6 100644 --- a/packages/account/src/test-utils/launchNode.ts +++ b/packages/account/src/test-utils/launchNode.ts @@ -1,4 +1,4 @@ -import { BYTES_32 } from '@fuel-ts/abi-coder'; +import { BYTES_32 } from '@fuel-ts/abi'; import { randomBytes, randomUUID } from '@fuel-ts/crypto'; import { FuelError } from '@fuel-ts/errors'; import type { SnapshotConfigs } from '@fuel-ts/utils'; diff --git a/packages/account/src/test-utils/resources.ts b/packages/account/src/test-utils/resources.ts index 688d985d698..a7fde002509 100644 --- a/packages/account/src/test-utils/resources.ts +++ b/packages/account/src/test-utils/resources.ts @@ -1,4 +1,4 @@ -import { BYTES_32, UTXO_ID_LEN } from '@fuel-ts/abi-coder'; +import { BYTES_32, UTXO_ID_LEN } from '@fuel-ts/abi'; import { Address } from '@fuel-ts/address'; import { ZeroBytes32 } from '@fuel-ts/address/configs'; import { randomBytes } from '@fuel-ts/crypto'; diff --git a/packages/account/src/test-utils/transactionRequest.ts b/packages/account/src/test-utils/transactionRequest.ts index a0aa0d66019..e224b326e77 100644 --- a/packages/account/src/test-utils/transactionRequest.ts +++ b/packages/account/src/test-utils/transactionRequest.ts @@ -1,4 +1,4 @@ -import { UTXO_ID_LEN } from '@fuel-ts/abi-coder'; +import { UTXO_ID_LEN } from '@fuel-ts/abi'; import { getRandomB256 } from '@fuel-ts/address'; import { ZeroBytes32 } from '@fuel-ts/address/configs'; import { randomBytes } from '@fuel-ts/crypto'; diff --git a/packages/account/src/utils/deployScriptOrPredicate.ts b/packages/account/src/utils/deployScriptOrPredicate.ts index 3ef7dad9c70..0cdc7cc9848 100644 --- a/packages/account/src/utils/deployScriptOrPredicate.ts +++ b/packages/account/src/utils/deployScriptOrPredicate.ts @@ -1,4 +1,4 @@ -import type { JsonAbi } from '@fuel-ts/abi-coder'; +import type { AbiSpecification } from '@fuel-ts/abi'; import { FuelError, ErrorCode } from '@fuel-ts/errors'; import { hash } from '@fuel-ts/hasher'; import { bn } from '@fuel-ts/math'; @@ -40,21 +40,21 @@ async function fundBlobTx(deployer: Account, blobTxRequest: BlobTransactionReque return deployer.fund(blobTxRequest, txCost); } -function adjustConfigurableOffsets(jsonAbi: JsonAbi, configurableOffsetDiff: number) { +function adjustConfigurableOffsets(jsonAbi: AbiSpecification, configurableOffsetDiff: number) { const { configurables: readOnlyConfigurables } = jsonAbi; - const configurables: JsonAbi['configurables'] = []; + const configurables: AbiSpecification['configurables'] = []; readOnlyConfigurables.forEach((config) => { // @ts-expect-error shut up the read-only thing configurables.push({ ...config, offset: config.offset - configurableOffsetDiff }); }); - return { ...jsonAbi, configurables } as JsonAbi; + return { ...jsonAbi, configurables } as AbiSpecification; } interface Deployer { deployer: Account; bytecode: Uint8Array; - abi: JsonAbi; - loaderInstanceCallback: (loaderBytecode: Uint8Array, newAbi: JsonAbi) => T; + abi: AbiSpecification; + loaderInstanceCallback: (loaderBytecode: Uint8Array, newAbi: AbiSpecification) => T; } export async function deployScriptOrPredicate({ diff --git a/packages/account/src/utils/formatTransferToContractScriptData.test.ts b/packages/account/src/utils/formatTransferToContractScriptData.test.ts index 956677fe332..196baa0beb0 100644 --- a/packages/account/src/utils/formatTransferToContractScriptData.test.ts +++ b/packages/account/src/utils/formatTransferToContractScriptData.test.ts @@ -1,4 +1,4 @@ -import { BigNumberCoder } from '@fuel-ts/abi-coder'; +import { AbiEncoding } from '@fuel-ts/abi'; import { getRandomB256 } from '@fuel-ts/address'; import type { BytesLike } from '@fuel-ts/interfaces'; import { bn, type BigNumberish } from '@fuel-ts/math'; @@ -50,7 +50,7 @@ describe('util', () => { const byte: number[] = [0, 0, 0, 0, 0, 0, 0, 1]; const encode = vi - .spyOn(BigNumberCoder.prototype, 'encode') + .spyOn(AbiEncoding.v1.coders.u64, 'encode') .mockReturnValue(Uint8Array.from(byte)); const arrayify = vi.spyOn(arrayifyMod, 'arrayify').mockReturnValue(Uint8Array.from(byte)); diff --git a/packages/account/src/utils/formatTransferToContractScriptData.ts b/packages/account/src/utils/formatTransferToContractScriptData.ts index d290ccbd949..beb6be6f9e2 100644 --- a/packages/account/src/utils/formatTransferToContractScriptData.ts +++ b/packages/account/src/utils/formatTransferToContractScriptData.ts @@ -1,4 +1,4 @@ -import { ASSET_ID_LEN, BigNumberCoder, CONTRACT_ID_LEN, WORD_SIZE } from '@fuel-ts/abi-coder'; +import { AbiEncoding, ASSET_ID_LEN, CONTRACT_ID_LEN, WORD_SIZE } from '@fuel-ts/abi'; import { Address } from '@fuel-ts/address'; import type { BytesLike } from '@fuel-ts/interfaces'; import { BN } from '@fuel-ts/math'; @@ -14,7 +14,7 @@ interface AssembleTransferToContractParams { export const formatTransferToContractScriptData = ( transferParams: Array ) => { - const numberCoder = new BigNumberCoder('u64'); + const numberCoder = AbiEncoding.v1.coders.u64; return transferParams.reduce((acc, transferParam) => { const { assetId, amount, contractId } = transferParam; const encoded = numberCoder.encode(new BN(amount).toNumber()); diff --git a/packages/account/test/fixtures/predicate-abi.ts b/packages/account/test/fixtures/predicate-abi.ts index 91bf0b52c06..aecbd61fb21 100644 --- a/packages/account/test/fixtures/predicate-abi.ts +++ b/packages/account/test/fixtures/predicate-abi.ts @@ -1,6 +1,6 @@ -import type { JsonAbi } from '@fuel-ts/abi-coder'; +import type { AbiSpecification } from '@fuel-ts/abi'; -export const predicateAbi: JsonAbi = { +export const predicateAbi: AbiSpecification = { programType: 'predicate', specVersion: '1', encodingVersion: '1', diff --git a/packages/account/test/fixtures/transaction-summary.ts b/packages/account/test/fixtures/transaction-summary.ts index 9ed7e2bd689..dff1e93034d 100644 --- a/packages/account/test/fixtures/transaction-summary.ts +++ b/packages/account/test/fixtures/transaction-summary.ts @@ -1,4 +1,4 @@ -import type { JsonAbi } from '@fuel-ts/abi-coder'; +import type { AbiSpecification } from '@fuel-ts/abi'; import { bn } from '@fuel-ts/math'; import type { InputCoin, @@ -263,7 +263,7 @@ export const MOCK_ABI_MAP: AbiMap = { loggedTypes: [], messagesTypes: [], configurables: [], - } as unknown as JsonAbi, // used in skipped test + } as unknown as AbiSpecification, // used in skipped test // packages/account/src/providers/transaction-summary/operations.test.ts // "should ensure getContractCallOperations return contract call operations with calls details" // when the test is unskipped, it'll fail and this mock can be adjusted or deleted diff --git a/packages/contract/package.json b/packages/contract/package.json index 74997bfa3f3..2a6e46b8f46 100644 --- a/packages/contract/package.json +++ b/packages/contract/package.json @@ -39,7 +39,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@fuel-ts/abi-coder": "workspace:*", + "@fuel-ts/abi": "workspace:*", "@fuel-ts/account": "workspace:*", "@fuel-ts/crypto": "workspace:*", "@fuel-ts/errors": "workspace:*", diff --git a/packages/contract/src/contract-factory.ts b/packages/contract/src/contract-factory.ts index 3427651eb40..1a06604a56c 100644 --- a/packages/contract/src/contract-factory.ts +++ b/packages/contract/src/contract-factory.ts @@ -1,5 +1,5 @@ -import { Interface, WORD_SIZE } from '@fuel-ts/abi-coder'; -import type { JsonAbi, InputValue } from '@fuel-ts/abi-coder'; +import { AbiCoder, WORD_SIZE } from '@fuel-ts/abi'; +import type { AbiSpecification, InputValue } from '@fuel-ts/abi'; import type { Account, CreateTransactionRequestLike, @@ -54,7 +54,7 @@ export type DeployContractResult = { */ export default class ContractFactory { bytecode: BytesLike; - interface: Interface; + interface: AbiCoder; provider!: Provider | null; account!: Account | null; storageSlots: StorageSlot[]; @@ -68,17 +68,17 @@ export default class ContractFactory { */ constructor( bytecode: BytesLike, - abi: JsonAbi | Interface, + abi: AbiSpecification | AbiCoder, accountOrProvider: Account | Provider | null = null, storageSlots: StorageSlot[] = [] ) { // Force the bytecode to be a byte array this.bytecode = arrayify(bytecode); - if (abi instanceof Interface) { + if (abi instanceof AbiCoder) { this.interface = abi; } else { - this.interface = new Interface(abi); + this.interface = AbiCoder.fromAbi(abi); } /** @@ -404,7 +404,7 @@ export default class ContractFactory { const { offset } = this.interface.configurables[key]; - const encoded = this.interface.encodeConfigurable(key, value as InputValue); + const encoded = this.interface.getConfigurable(key).encode(value as InputValue); const bytes = arrayify(this.bytecode); diff --git a/packages/contract/src/loader/loader-script.test.ts b/packages/contract/src/loader/loader-script.test.ts index 31511569f21..edec0b7de26 100644 --- a/packages/contract/src/loader/loader-script.test.ts +++ b/packages/contract/src/loader/loader-script.test.ts @@ -1,4 +1,4 @@ -import { WORD_SIZE } from '@fuel-ts/abi-coder'; +import { WORD_SIZE } from '@fuel-ts/abi'; import { arrayify } from '@fuel-ts/utils'; import { getLoaderInstructions } from './loader-script'; diff --git a/packages/contract/src/loader/loader-script.ts b/packages/contract/src/loader/loader-script.ts index 6d9c64e1254..d733286fd87 100644 --- a/packages/contract/src/loader/loader-script.ts +++ b/packages/contract/src/loader/loader-script.ts @@ -1,4 +1,4 @@ -import { BYTES_32 } from '@fuel-ts/abi-coder'; +import { BYTES_32 } from '@fuel-ts/abi'; import { InstructionSet } from '@fuel-ts/program'; import { arrayify, concat } from '@fuel-ts/utils'; import * as asm from '@fuels/vm-asm'; diff --git a/packages/contract/src/loader/utils.test.ts b/packages/contract/src/loader/utils.test.ts index 6e306271160..32cb2843f76 100644 --- a/packages/contract/src/loader/utils.test.ts +++ b/packages/contract/src/loader/utils.test.ts @@ -1,4 +1,4 @@ -import { WORD_SIZE } from '@fuel-ts/abi-coder'; +import { WORD_SIZE } from '@fuel-ts/abi'; import { getContractChunks } from './utils'; diff --git a/packages/contract/src/loader/utils.ts b/packages/contract/src/loader/utils.ts index 75528aab5b4..ae85e5038e2 100644 --- a/packages/contract/src/loader/utils.ts +++ b/packages/contract/src/loader/utils.ts @@ -1,4 +1,4 @@ -import { WORD_SIZE } from '@fuel-ts/abi-coder'; +import { WORD_SIZE } from '@fuel-ts/abi'; import { concat } from '@fuel-ts/utils'; export const getContractChunks = (bytecode: Uint8Array, chunkSize: number) => { diff --git a/packages/contract/src/test-utils/launch-test-node.test.ts b/packages/contract/src/test-utils/launch-test-node.test.ts index bb729be49da..37e7c0349e8 100644 --- a/packages/contract/src/test-utils/launch-test-node.test.ts +++ b/packages/contract/src/test-utils/launch-test-node.test.ts @@ -1,4 +1,4 @@ -import type { JsonAbi } from '@fuel-ts/abi-coder'; +import type { AbiSpecification } from '@fuel-ts/abi'; import { Provider } from '@fuel-ts/account'; import * as setupTestProviderAndWalletsMod from '@fuel-ts/account/test-utils'; import { randomBytes, randomUUID } from '@fuel-ts/crypto'; @@ -15,7 +15,7 @@ import ContractFactory from '../contract-factory'; import { launchTestNode } from './launch-test-node'; -const { binHexlified, abiContents } = getForcProject({ +const { binHexlified, abiContents } = getForcProject({ projectDir: join(__dirname, '../../test/fixtures/forc-projects/simple-contract'), projectName: 'simple-contract', build: 'release', diff --git a/packages/fuel-gauge/src/abi/abi-coder.test.ts b/packages/fuel-gauge/src/abi/abi-coder.test.ts index cf3b9459fbd..e4a8435438c 100644 --- a/packages/fuel-gauge/src/abi/abi-coder.test.ts +++ b/packages/fuel-gauge/src/abi/abi-coder.test.ts @@ -1,4 +1,4 @@ -import { bn, FuelError, getRandomB256 } from 'fuels'; +import { bn, ContractFactory, FuelError, getRandomB256 } from 'fuels'; import type { AssetId, BigNumberish, EvmAddress, RawSlice, WalletUnlocked } from 'fuels'; import { expectToThrowFuelError, launchTestNode } from 'fuels/test-utils'; @@ -55,7 +55,7 @@ expect.extend({ toEqualBn }); * @group browser * @group node */ -describe('AbiCoder', () => { +describe.skip('AbiCoder', () => { let contract: AbiContract; let wallet: WalletUnlocked; let cleanup: () => void; @@ -68,7 +68,8 @@ describe('AbiCoder', () => { const { contracts, wallets } = launched; wallet = wallets[0]; - contract = contracts[0] as AbiContract; + contract = contracts[0]; + cleanup = launched.cleanup; }); @@ -107,7 +108,8 @@ describe('AbiCoder', () => { }, }; - const { waitForResult: waitForDeploy } = await AbiContractFactory.deploy(wallet, { + const factory = new ContractFactory(AbiContractFactory.bytecode, contract.interface, wallet); + const { waitForResult: waitForDeploy } = await factory.deploy({ configurableConstants: NEW_CONFIGURABLES, }); @@ -132,6 +134,68 @@ describe('AbiCoder', () => { }); }); + describe('attributes', () => { + it('should have storage read attribute', () => { + const fn = contract.interface.getFunction('attributes_storage_read'); + expect(fn.attributes).toEqual([{ name: 'storage', arguments: ['read'] }]); + }); + + it('should have storage write attribute', () => { + const fn = contract.interface.getFunction('attributes_storage_write'); + expect(fn.attributes).toEqual([{ name: 'storage', arguments: ['write'] }]); + }); + + it('should have storage read and write attribute', () => { + const fn = contract.interface.getFunction('attributes_storage_read_write'); + expect(fn.attributes).toEqual([{ name: 'storage', arguments: ['read', 'write'] }]); + }); + + it('should have payable attribute', () => { + const fn = contract.interface.getFunction('attributes_payable'); + expect(fn.attributes).toEqual([{ name: 'payable' }]); + }); + + it('should have test attribute', () => { + const fn = contract.interface.getFunction('attributes_test'); + expect(fn.attributes).toEqual([{ name: 'test' }]); + }); + + it('should have inline never attribute', () => { + const fn = contract.interface.getFunction('attributes_inline_never'); + expect(fn.attributes).toEqual([{ name: 'inline', arguments: 'never' }]); + }); + + it('should have inline always attribute', () => { + const fn = contract.interface.getFunction('attributes_inline_always'); + expect(fn.attributes).toEqual([{ name: 'inline', arguments: 'always' }]); + }); + + it('should have doc attribute', () => { + const fn = contract.interface.getFunction('attributes_doc_comment'); + expect(fn.attributes).toEqual([ + { name: 'doc-comment', arguments: [' This is a doc'] }, + { name: 'doc-comment', arguments: [' This is another doc comment'] }, + ]); + }); + }); + + describe('isReadOnly', () => { + it('should return true for a storage read function', () => { + const fn = contract.interface.getFunction('attributes_storage_read'); + expect(fn.isReadOnly()).toBe(true); + }); + + it('should return true for a function that does not use storage', () => { + const fn = contract.interface.getFunction('attributes_none'); + expect(fn.isReadOnly()).toBe(true); + }); + + it('should return false for a storage write function', () => { + const fn = contract.interface.getFunction('attributes_storage_write'); + expect(fn.isReadOnly()).toBe(false); + }); + }); + describe('types_u8', () => { it('should encode/decode just fine', async () => { const input = 8; @@ -1013,7 +1077,7 @@ describe('AbiCoder', () => { describe('types_struct_with_array', () => { /** - * TODO: This is causing a generic to be left into the parsed format. + * @TODO This is causing a generic to be left into the parsed format, ask Nedim about this. */ it.skip('should encode/decode just fine', async () => { // Inputs @@ -1233,7 +1297,7 @@ describe('AbiCoder', () => { }); describe('types_struct_with_multiple_struct_params', () => { - it.todo('should encode/decode just fine', async () => { + it('should encode/decode just fine', async () => { const STRUCT_A = { propA1: 10 }; const STRUCT_B = { propB1: STRUCT_A, propB2: 20 }; @@ -1253,8 +1317,7 @@ describe('AbiCoder', () => { .types_struct_with_multiple_struct_params(INPUT_X, INPUT_Y, INPUT_Z) .call(); - await waitForResult(); - // const { value, logs } = await waitForResult(); + const { value, logs } = await waitForResult(); // expect(value).toStrictEqual(expected); // expect(logs).toStrictEqual([expected]); }); @@ -1279,7 +1342,7 @@ describe('AbiCoder', () => { }); describe('types_struct_with_complex_nested_struct', () => { - it.todo('should encode/decode just fine'); + it('should encode/decode just fine'); it('should have function properties', () => { const fn = contract.interface.getFunction('types_struct_with_complex_nested_struct'); diff --git a/packages/fuel-gauge/src/abi/abi-gen.test.ts b/packages/fuel-gauge/src/abi/abi-gen.test.ts deleted file mode 100644 index e3a2b44922a..00000000000 --- a/packages/fuel-gauge/src/abi/abi-gen.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { log } from 'console'; - -import { AbiProjectsEnum, getAbiForcProject } from './utils'; - -/** - * @group node - */ -describe('AbiGen', () => { - test('contract', () => { - const { abiContents } = getAbiForcProject(AbiProjectsEnum.ABI_CONTRACT); - log(abiContents); - }); - - test('script', () => { - const { abiContents } = getAbiForcProject(AbiProjectsEnum.ABI_SCRIPT); - log(abiContents); - }); - - test('predicate', () => { - const { abiContents } = getAbiForcProject(AbiProjectsEnum.ABI_PREDICATE); - log(abiContents); - }); -}); diff --git a/packages/fuel-gauge/src/abi/coder/multi-argument.test.ts b/packages/fuel-gauge/src/abi/coder/multi-argument.test.ts new file mode 100644 index 00000000000..9ea3b92ab64 --- /dev/null +++ b/packages/fuel-gauge/src/abi/coder/multi-argument.test.ts @@ -0,0 +1,226 @@ +import type { AbiSpecificationV1 } from '@fuel-ts/abi'; +import { AbiCoder } from '@fuel-ts/abi'; +import { concat } from 'fuels'; + +import { B256_DECODED, B256_ENCODED, U32_MAX, U32_MAX_ENCODED, U8_MAX } from '../constants'; +import { AbiProjectsEnum, getAbiForcProject } from '../utils'; +import { toEqualBn } from '../vitest.matcher'; + +const { abiContents: contractAbi } = getAbiForcProject(AbiProjectsEnum.ABI_CONTRACT); +const contract = AbiCoder.fromAbi(contractAbi as AbiSpecificationV1); + +expect.extend({ toEqualBn }); + +describe('multi_arg_u64_u64', () => { + const fn = contract.functions.multi_arg_u64_u64; + + describe('encode', () => { + it('should encode value [U64_MAX, U64_MAX]', () => { + const value: [number, number] = [U8_MAX, U8_MAX]; + const expected = concat([ + new Uint8Array([0, 0, 0, 0, 0, 0, 0, U8_MAX]), + new Uint8Array([0, 0, 0, 0, 0, 0, 0, U8_MAX]), + ]); + + const actual = fn.arguments.encode(value); + + expect(actual).toStrictEqual(expected); + }); + }); + + describe('decode', () => { + it('should decode value [U64_MAX, U64_MAX]', () => { + const value = concat([ + new Uint8Array([0, 0, 0, 0, 0, 0, 0, U8_MAX]), + new Uint8Array([0, 0, 0, 0, 0, 0, 0, U8_MAX]), + ]); + // @ts-expect-error toEqualBn is not a function + const expected: [number, number] = [expect.toEqualBn(U8_MAX), expect.toEqualBn(U8_MAX)]; + + const actual = fn.arguments.decode(value); + + expect(actual).toStrictEqual(expected); + }); + }); +}); + +describe('multi_arg_vector_b256', () => { + const fn = contract.functions.multi_arg_vector_b256; + + describe('encode', () => { + it('should encode value [[U8_MAX, 0, U8_MAX, U8_MAX], B256_DECODED]', () => { + const value: [number[], string] = [[U8_MAX, 0, U8_MAX, U8_MAX], B256_DECODED]; + const expected = concat([ + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 4]), + new Uint8Array([U8_MAX, 0, U8_MAX, U8_MAX]), + B256_ENCODED, + ]); + + const actual = fn.arguments.encode(value); + + expect(actual).toStrictEqual(expected); + }); + }); + + describe('decode', () => { + it('should decode value [[U8_MAX, 0, U8_MAX, U8_MAX], B256_DECODED]', () => { + const value = concat([ + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 4]), + new Uint8Array([U8_MAX, 0, U8_MAX, U8_MAX]), + B256_ENCODED, + ]); + const expected: [number[], string] = [[U8_MAX, 0, U8_MAX, U8_MAX], B256_DECODED]; + + const actual = fn.arguments.decode(value); + + expect(actual).toStrictEqual(expected); + }); + }); +}); + +describe('multi_arg_struct_vector', () => { + const fn = contract.functions.multi_arg_struct_vector; + + describe('encode', () => { + it('should encode value [{ a, b }, [U8_MAX, 0, U8_MAX, U8_MAX]]', () => { + const value: [{ a: boolean; b: number }, number[]] = [ + { a: true, b: U8_MAX }, + [U8_MAX, 0, U8_MAX, U8_MAX], + ]; + const expected = concat([ + new Uint8Array([1]), + new Uint8Array([0, 0, 0, U8_MAX]), + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 4]), + new Uint8Array([U8_MAX, 0, U8_MAX, U8_MAX]), + ]); + + const actual = fn.arguments.encode(value); + + expect(actual).toStrictEqual(expected); + }); + }); + + describe('decode', () => { + it('should decode value [{ a, b }, [U8_MAX, 0, U8_MAX, U8_MAX]]', () => { + const value = concat([ + new Uint8Array([1]), + new Uint8Array([0, 0, 0, U8_MAX]), + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 4]), + new Uint8Array([U8_MAX, 0, U8_MAX, U8_MAX]), + ]); + const expected: [{ a: boolean; b: number }, number[]] = [ + { a: true, b: U8_MAX }, + [U8_MAX, 0, U8_MAX, U8_MAX], + ]; + + const actual = fn.arguments.decode(value); + + expect(actual).toStrictEqual(expected); + }); + }); +}); + +describe('multi_arg_vector_vector', () => { + const fn = contract.functions.multi_arg_vector_vector; + + describe('encode', () => { + it('should encode value [[U8_MAX, 0], [U8_MAX, 0, U8_MAX, U8_MAX]]', () => { + const value: [number[], number[]] = [ + [U8_MAX, 0], + [U8_MAX, 0, U8_MAX, U8_MAX], + ]; + const expected = concat([ + // Vector 1 + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 2]), + new Uint8Array([U8_MAX, 0]), + // Vector 2 + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 4]), + new Uint8Array([U8_MAX, 0, U8_MAX, U8_MAX]), + ]); + + const actual = fn.arguments.encode(value); + + expect(actual).toStrictEqual(expected); + }); + }); + + describe('decode', () => { + it('should decode value [[U8_MAX, 0], [U8_MAX, 0, U8_MAX, U8_MAX]]', () => { + const value = concat([ + // Vector 1 + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 2]), + new Uint8Array([U8_MAX, 0]), + // Vector 2 + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 4]), + new Uint8Array([U8_MAX, 0, U8_MAX, U8_MAX]), + ]); + const expected: [number[], number[]] = [ + [U8_MAX, 0], + [U8_MAX, 0, U8_MAX, U8_MAX], + ]; + + const actual = fn.arguments.decode(value); + + expect(actual).toStrictEqual(expected); + }); + }); +}); + +describe('multi_arg_u32_vector_vector', () => { + const fn = contract.functions.multi_arg_u32_vector_vector; + + describe('encode', () => { + it('should encode value [U8_MAX, [[U8_MAX, 0], [U8_MAX, 0, U8_MAX, U8_MAX]]]', () => { + const value = [U32_MAX, [123, 0], [124, 0, 125, 126]]; + const expected = concat([ + // U32 + U32_MAX_ENCODED, + // Vector 1 + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 2]), + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 123]), // Vector 1 - element 0 + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]), // Vector 1 - element 1 + // Vector 2 + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 4]), + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 124]), // Vector 2 - element 0 + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]), // Vector 2 - element 1 + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 125]), // Vector 2 - element 2 + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 126]), // Vector 2 - element 3 + ]); + + const actual = fn.arguments.encode(value); + + expect(actual).toStrictEqual(expected); + }); + }); + + describe('decode', () => { + it('should decode value [U8_MAX, [[U8_MAX, 0], [U8_MAX, 0, U8_MAX, U8_MAX]]]', () => { + const value = concat([ + // U32 + U32_MAX_ENCODED, + // Vector 1 + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 2]), + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 123]), // Vector 1 - element 0 + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]), // Vector 1 - element 1 + // Vector 2 + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 4]), + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 124]), // Vector 2 - element 0 + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]), // Vector 2 - element 1 + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 125]), // Vector 2 - element 2 + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 126]), // Vector 2 - element 3 + ]); + const expected = [ + // @ts-expect-error toEqualBn is not a function + expect.toEqualBn(U32_MAX), + // @ts-expect-error toEqualBn is not a function + [expect.toEqualBn(123), expect.toEqualBn(0)], + // @ts-expect-error toEqualBn is not a function + [expect.toEqualBn(124), expect.toEqualBn(0), expect.toEqualBn(125), expect.toEqualBn(126)], + ]; + + const actual = fn.arguments.decode(value); + + expect(actual).toStrictEqual(expected); + }); + }); +}); diff --git a/packages/fuel-gauge/src/abi/encoding/b256.test.ts b/packages/fuel-gauge/src/abi/encoding/b256.test.ts new file mode 100644 index 00000000000..0ff9bdbd4a2 --- /dev/null +++ b/packages/fuel-gauge/src/abi/encoding/b256.test.ts @@ -0,0 +1,90 @@ +import type { AbiSpecificationV1 } from '@fuel-ts/abi'; +import { AbiCoder } from '@fuel-ts/abi'; +import { FuelError, toBytes } from 'fuels'; +import { expectToThrowFuelError } from 'fuels/test-utils'; + +import { B256_DECODED, B256_ENCODED } from '../constants'; +import { AbiProjectsEnum, getAbiForcProject } from '../utils'; + +const { abiContents: contractAbi } = getAbiForcProject(AbiProjectsEnum.ABI_CONTRACT); +const contract = AbiCoder.fromAbi(contractAbi as AbiSpecificationV1); + +describe('types_b256', () => { + const fn = contract.functions.types_b256; + + describe('encode', () => { + it('should encode value [valid b256 hex]', () => { + const value = B256_DECODED; + const expected = B256_ENCODED; + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it.todo('should fail to encode value [too short]', async () => { + const value = B256_DECODED.slice(-1); + + await expectToThrowFuelError( + () => fn.encodeOutput(value), + new FuelError(FuelError.CODES.ENCODE_ERROR, 'Invalid b256 value.') + ); + }); + + it.todo('should fail to encode value [too long]', async () => { + const value = `${B256_DECODED}PLUS`; + + await expectToThrowFuelError( + () => fn.encodeOutput(value), + new FuelError(FuelError.CODES.ENCODE_ERROR, 'Invalid b256 value.') + ); + }); + + it.todo('should fail to encode value [not a hex]', async () => { + const value = `not a hex value`; + + await expectToThrowFuelError( + () => fn.encodeOutput(value), + new FuelError(FuelError.CODES.ENCODE_ERROR, 'Invalid b256 value.') + ); + }); + }); + + describe('decode', () => { + it('should decode value [valid b256 hex]', () => { + const value = B256_ENCODED; + const expected = B256_DECODED; + + const actual = fn.decodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it.todo('should fail to decode value [too short]', async () => { + const value = B256_ENCODED.slice(-1); + + await expectToThrowFuelError( + () => fn.decodeOutput(value), + new FuelError(FuelError.CODES.DECODE_ERROR, 'Invalid b256 value.') + ); + }); + + it.todo('should fail to decode value [too long]', async () => { + const value = new Uint8Array([...B256_ENCODED, 0]); + + await expectToThrowFuelError( + () => fn.decodeOutput(value), + new FuelError(FuelError.CODES.DECODE_ERROR, 'Invalid b256 value.') + ); + }); + + it.todo('should fail to decode value [not hex]', async () => { + const value = toBytes('not a hex value'); + + await expectToThrowFuelError( + () => fn.decodeOutput(value), + new FuelError(FuelError.CODES.DECODE_ERROR, 'Invalid b256 value.') + ); + }); + }); +}); diff --git a/packages/fuel-gauge/src/abi/encoding/b512.test.ts b/packages/fuel-gauge/src/abi/encoding/b512.test.ts new file mode 100644 index 00000000000..7ee5861daf0 --- /dev/null +++ b/packages/fuel-gauge/src/abi/encoding/b512.test.ts @@ -0,0 +1,90 @@ +import type { AbiSpecificationV1 } from '@fuel-ts/abi'; +import { AbiCoder } from '@fuel-ts/abi'; +import { FuelError, toBytes } from 'fuels'; +import { expectToThrowFuelError } from 'fuels/test-utils'; + +import { B512_DECODED, B512_ENCODED } from '../constants'; +import { AbiProjectsEnum, getAbiForcProject } from '../utils'; + +const { abiContents: contractAbi } = getAbiForcProject(AbiProjectsEnum.ABI_CONTRACT); +const contract = AbiCoder.fromAbi(contractAbi as AbiSpecificationV1); + +describe('types_b512', () => { + const fn = contract.functions.types_b512; + + describe('encode', () => { + it('should encode value [valid b512 hex]', () => { + const value = B512_DECODED; + const expected = B512_ENCODED; + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it.todo('should fail to encode value [too short]', async () => { + const value = B512_DECODED.slice(-1); + + await expectToThrowFuelError( + () => fn.encodeOutput(value), + new FuelError(FuelError.CODES.ENCODE_ERROR, 'Invalid b512 value.') + ); + }); + + it.todo('should fail to encode value [too long]', async () => { + const value = `${B512_DECODED}PLUS`; + + await expectToThrowFuelError( + () => fn.encodeOutput(value), + new FuelError(FuelError.CODES.ENCODE_ERROR, 'Invalid b512 value.') + ); + }); + + it.todo('should fail to encode value [not a hex]', async () => { + const value = `not a hex value`; + + await expectToThrowFuelError( + () => fn.encodeOutput(value), + new FuelError(FuelError.CODES.ENCODE_ERROR, 'Invalid b512 value.') + ); + }); + }); + + describe('decode', () => { + it('should decode value [valid b512 hex]', () => { + const value = B512_ENCODED; + const expected = B512_DECODED; + + const actual = fn.decodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it.todo('should fail to decode value [too short]', async () => { + const value = B512_ENCODED.slice(-1); + + await expectToThrowFuelError( + () => fn.decodeOutput(value), + new FuelError(FuelError.CODES.DECODE_ERROR, 'Invalid b512 value.') + ); + }); + + it.todo('should fail to decode value [too long]', async () => { + const value = new Uint8Array([...B512_ENCODED, 0]); + + await expectToThrowFuelError( + () => fn.decodeOutput(value), + new FuelError(FuelError.CODES.DECODE_ERROR, 'Invalid b512 value.') + ); + }); + + it.todo('should fail to decode value [not hex]', async () => { + const value = toBytes('not a hex value'); + + await expectToThrowFuelError( + () => fn.decodeOutput(value), + new FuelError(FuelError.CODES.DECODE_ERROR, 'Invalid b512 value.') + ); + }); + }); +}); diff --git a/packages/fuel-gauge/src/abi/encoding/bool.test.ts b/packages/fuel-gauge/src/abi/encoding/bool.test.ts new file mode 100644 index 00000000000..79f7cf940c5 --- /dev/null +++ b/packages/fuel-gauge/src/abi/encoding/bool.test.ts @@ -0,0 +1,89 @@ +import type { AbiSpecificationV1 } from '@fuel-ts/abi'; +import { AbiCoder } from '@fuel-ts/abi'; +import { FuelError, toBytes } from 'fuels'; +import { expectToThrowFuelError } from 'fuels/test-utils'; + +import { AbiProjectsEnum, getAbiForcProject } from '../utils'; + +const { abiContents: contractAbi } = getAbiForcProject(AbiProjectsEnum.ABI_CONTRACT); +const contract = AbiCoder.fromAbi(contractAbi as AbiSpecificationV1); + +describe('types_bool', () => { + const fn = contract.functions.types_bool; + + describe('encode', () => { + it('should encode value [true]', () => { + const value = true; + const expected = new Uint8Array([1]); + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it('should encode value [false]', () => { + const value = false; + const expected = new Uint8Array([0]); + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it.todo('should fail to encode value [max + 1]', async () => { + const value = 2; + + await expectToThrowFuelError( + () => fn.encodeOutput(value), + new FuelError(FuelError.CODES.ENCODE_ERROR, 'Invalid bool value.') + ); + }); + + it.todo('should fail to encode value [string]', async () => { + const value = 'true'; + + await expectToThrowFuelError( + () => fn.encodeOutput(value), + new FuelError(FuelError.CODES.ENCODE_ERROR, 'Invalid bool value.') + ); + }); + }); + + describe('decode', () => { + it('should decode value [true]', () => { + const value = new Uint8Array([1]); + const expected = true; + + const actual = fn.decodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it('should decode value [false]', () => { + const value = new Uint8Array([0]); + const expected = false; + + const actual = fn.decodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it.todo('should fail to decode value [max + 1]', async () => { + const value = new Uint8Array([2]); + + await expectToThrowFuelError( + () => fn.encodeOutput(value), + new FuelError(FuelError.CODES.DECODE_ERROR, 'Invalid bool value.') + ); + }); + + it.todo('should fail to decode value [string]', async () => { + const value = toBytes('true'); + + await expectToThrowFuelError( + () => fn.encodeOutput(value), + new FuelError(FuelError.CODES.DECODE_ERROR, 'Invalid bool value.') + ); + }); + }); +}); diff --git a/packages/fuel-gauge/src/abi/encoding/enum.test.ts b/packages/fuel-gauge/src/abi/encoding/enum.test.ts new file mode 100644 index 00000000000..344314268e7 --- /dev/null +++ b/packages/fuel-gauge/src/abi/encoding/enum.test.ts @@ -0,0 +1,229 @@ +import type { AbiSpecificationV1 } from '@fuel-ts/abi'; +import { AbiCoder } from '@fuel-ts/abi'; +import { concat } from 'fuels'; + +import { + BOOL_TRUE_ENCODED, + U32_MAX, + U32_MAX_ENCODED, + U64_MAX, + U64_MAX_ENCODED, +} from '../constants'; +import { AbiProjectsEnum, getAbiForcProject } from '../utils'; +import { toEqualBn } from '../vitest.matcher'; + +const { abiContents: contractAbi } = getAbiForcProject(AbiProjectsEnum.ABI_CONTRACT); +const contract = AbiCoder.fromAbi(contractAbi as AbiSpecificationV1); + +expect.extend({ toEqualBn }); + +// @TODO implement native enums +describe.todo('types_enum', () => { + const fn = contract.functions.types_enum; + + describe('encode', () => { + it('should encode value [Pending]', () => { + const value = 'Pending'; + const expected = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 1]); + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + }); + + describe('decode', () => { + it('should decode value [Pending]', () => { + const value = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 1]); + const expected = 'Pending'; + + const actual = fn.decodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + }); +}); + +describe('types_enum_with_builtin_type', () => { + const fn = contract.functions.types_enum_with_builtin_type; + + describe('encode', () => { + it('should encode value [{ a: true }]', () => { + const value = { + a: true, + }; + const expected = concat([ + // Case key (index: 0) + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]), + + // Case value + new Uint8Array([1]), + ]); + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it('should encode value [{ b: U64_MAX }]', () => { + const value = { + b: '18446744073709551615', + }; + const expected = concat([ + // Case key (index: 1) + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 1]), + + // Case value + new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255]), + ]); + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + }); + + describe('decode', () => { + it('should decode value [{ a: true }]', () => { + const value = concat([ + // Case key (index: 0) + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]), + + // Case value + new Uint8Array([1]), + ]); + const expected = { + a: true, + }; + + const actual = fn.decodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it('should decode value [{ b: U64_MAX }]', () => { + const value = concat([ + // Case key (index: 1) + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 1]), + + // Case value + new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255]), + ]); + const expected = { + // @ts-expect-error toEqualBn is not a function + b: expect.toEqualBn(U64_MAX), + }; + + const actual = fn.decodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + }); +}); + +describe('types_enum_with_vector', () => { + const fn = contract.functions.types_enum_with_vector; + + describe('encode', () => { + it('should encode value [{ b: [1, 2] }]', () => { + const value = { + b: [1, 2], + }; + const expected = concat([ + // Case key (index: 1) + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 1]), + + // Case value + // Vector length + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 2]), + // Vector data + new Uint8Array([1, 2]), + ]); + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + }); + + describe('decode', () => { + it('should decode value [{ b: [1, 2] }]', () => { + const value = concat([ + // Case key (index: 1) + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 1]), + + // Case value + // Vector length + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 2]), + // Vector + new Uint8Array([1, 2]), + ]); + const expected = { + b: [1, 2], + }; + + const actual = fn.decodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + }); +}); + +describe('types_enum_with_structs', () => { + const fn = contract.functions.types_enum_with_structs; + + describe('encode', () => { + it('should encode value [{ c: { propA1: U64_MAX, propA2: { a: true, b: U32_MAX } } }]', () => { + const value = { + c: { + a: U64_MAX, + b: { + a: true, + b: U32_MAX, + }, + }, + }; + const expected = concat([ + // Case key (index: 2) + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 2]), + + // Case value + U64_MAX_ENCODED, + BOOL_TRUE_ENCODED, + U32_MAX_ENCODED, + ]); + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + }); + + describe('decode', () => { + it('should decode value [{ c: { propA1: U64_MAX, propA2: { a: true, b: U32_MAX } } }]', () => { + const value = concat([ + // Case key (index: 2) + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 2]), + + // Case value + U64_MAX_ENCODED, + BOOL_TRUE_ENCODED, + U32_MAX_ENCODED, + ]); + const expected = { + c: { + // @ts-expect-error toEqualBn is not a function + a: expect.toEqualBn(U64_MAX), + b: { + a: true, + b: U32_MAX, + }, + }, + }; + + const actual = fn.decodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + }); +}); diff --git a/packages/fuel-gauge/src/abi/encoding/heap.test.ts b/packages/fuel-gauge/src/abi/encoding/heap.test.ts new file mode 100644 index 00000000000..a4d9938a201 --- /dev/null +++ b/packages/fuel-gauge/src/abi/encoding/heap.test.ts @@ -0,0 +1,87 @@ +import type { AbiSpecificationV1 } from '@fuel-ts/abi'; +import { AbiCoder } from '@fuel-ts/abi'; + +import { AbiProjectsEnum, getAbiForcProject } from '../utils'; + +const { abiContents: contractAbi } = getAbiForcProject(AbiProjectsEnum.ABI_CONTRACT); +const contract = AbiCoder.fromAbi(contractAbi as AbiSpecificationV1); + +describe('types_bytes', () => { + const fn = contract.functions.types_bytes; + + describe('encode', () => { + it('should encode value [valid byte]', () => { + const value = new Uint8Array([1, 2, 3]); + const expected: Uint8Array = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 3, 1, 2, 3]); + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + }); + + describe('decode', () => { + it('should decode value [valid byte]', () => { + const value = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 3, 1, 2, 3]); + const expected: Uint8Array = new Uint8Array([1, 2, 3]); + + const actual = fn.decodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + }); +}); + +describe('types_raw_slice', () => { + const fn = contract.functions.types_raw_slice; + + describe('encode', () => { + it('should encode value [valid number array]', () => { + const value: number[] = [1, 2, 3]; + const expected = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 3, 1, 2, 3]); + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + }); + + describe('decode', () => { + it('should decode value [valid bytes]', () => { + const value = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 3, 1, 2, 3]); + const expected: number[] = [1, 2, 3]; + + const actual = fn.decodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + }); +}); + +describe('types_str_slice', () => { + const fn = contract.functions.types_str_slice; + + describe('encode', () => { + it('should encode value [valid string]', () => { + const value: string = 'hello'; + const expected: Uint8Array = new Uint8Array([ + 0, 0, 0, 0, 0, 0, 0, 5, 104, 101, 108, 108, 111, + ]); + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + }); + + describe('decode', () => { + it('should decode value [valid byte]', () => { + const value: Uint8Array = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 5, 104, 101, 108, 108, 111]); + const expected: string = 'hello'; + + const actual = fn.decodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + }); +}); diff --git a/packages/fuel-gauge/src/abi/encoding/struct.test.ts b/packages/fuel-gauge/src/abi/encoding/struct.test.ts new file mode 100644 index 00000000000..8e275f911a3 --- /dev/null +++ b/packages/fuel-gauge/src/abi/encoding/struct.test.ts @@ -0,0 +1,305 @@ +import type { AbiSpecificationV1 } from '@fuel-ts/abi'; +import { AbiCoder } from '@fuel-ts/abi'; +import { concat, FuelError, toBytes } from 'fuels'; +import { expectToThrowFuelError } from 'fuels/test-utils'; + +import { + B256_DECODED, + B256_ENCODED, + BOOL_FALSE_ENCODED, + BOOL_TRUE_ENCODED, + U32_MAX, + U32_MAX_ENCODED, + U32_MIN, + U32_MIN_ENCODED, + U64_MAX, + U64_MAX_ENCODED, + U8_MAX, + U8_MAX_ENCODED, +} from '../constants'; +import { AbiProjectsEnum, getAbiForcProject } from '../utils'; +import { toEqualBn } from '../vitest.matcher'; + +const { abiContents: contractAbi } = getAbiForcProject(AbiProjectsEnum.ABI_CONTRACT); +const contract = AbiCoder.fromAbi(contractAbi as AbiSpecificationV1); + +expect.extend({ toEqualBn }); + +/** + * @TODO + * - [ ] Better validation messages... 'Types/values length mismatch.' + * Where did the error occur? "fieldB" and/or "fieldB" (maybe an array of field names that failed) + * Why did it occur? (should be captured from the underlying field coder) + */ +describe('types_struct_simple', () => { + const fn = contract.functions.types_struct_simple; + + describe('encode', () => { + it('should encode value [{ a: true, b: U32_MAX }]', () => { + const value = { a: true, b: U32_MAX }; + const expected = concat([BOOL_TRUE_ENCODED, U32_MAX_ENCODED]); + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it('should encode value [{ b: U32_MIN, a: false }]', () => { + const value = { b: U32_MIN, a: false }; + const expected = concat([BOOL_FALSE_ENCODED, U32_MIN_ENCODED]); + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it.todo('should fail to encode value [missing property]', async () => { + const value = { a: false }; + + await expectToThrowFuelError( + () => fn.encodeOutput(value), + new FuelError( + FuelError.CODES.ENCODE_ERROR, + 'Invalid struct SimpleStruct. Field "b" not present.' + ) + ); + }); + + it.todo('should fail to encode value [additional property]', async () => { + const value = { a: false, b: U32_MIN, naz: 'gûl' }; + + await expectToThrowFuelError( + () => fn.encodeOutput(value), + new FuelError(FuelError.CODES.ENCODE_ERROR, 'Types/values length mismatch.') + ); + }); + }); + + describe('decode', () => { + it('should decode value [{ a: true, b: U32_MAX }]', () => { + const value = concat([BOOL_TRUE_ENCODED, U32_MAX_ENCODED]); + const expected = { a: true, b: U32_MAX }; + + const actual = fn.decodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it('should decode value [{ b: U32_MIN, a: false }]', () => { + const value = concat([BOOL_FALSE_ENCODED, U32_MIN_ENCODED]); + const expected = { b: U32_MIN, a: false }; + + const actual = fn.decodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it.todo('should fail to decode value [missing property]', async () => { + const value = concat([BOOL_FALSE_ENCODED]); + + await expectToThrowFuelError( + () => fn.decodeOutput(value), + new FuelError( + FuelError.CODES.DECODE_ERROR, + 'Invalid struct SimpleStruct. Field "b" not present.' + ) + ); + }); + + it.todo('should fail to decode value [additional property]', async () => { + const value = concat([BOOL_FALSE_ENCODED, U32_MIN_ENCODED, toBytes('gûl')]); + + await expectToThrowFuelError( + () => fn.decodeOutput(value), + new FuelError(FuelError.CODES.DECODE_ERROR, 'Types/values length mismatch.') + ); + }); + }); +}); + +describe('types_struct_generic', () => { + const fn = contract.functions.types_struct_generic; + + describe('encode', () => { + it('should encode value [{ a: U8_MAX }]', () => { + const value = { a: U8_MAX }; + const expected = U8_MAX_ENCODED; + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + }); + + describe('decode', () => { + it('should decode value [{ a: true, b: U32_MAX }]', () => { + const value = U8_MAX_ENCODED; + const expected = { a: U8_MAX }; + + const actual = fn.decodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + }); +}); + +describe('types_struct_with_tuple', () => { + const fn = contract.functions.types_struct_with_tuple; + + describe('encode', () => { + it('should encode value [{ a: [true, U64_MAX] }]', () => { + const value = { a: [true, U64_MAX] }; + const expected = concat([BOOL_TRUE_ENCODED, U64_MAX_ENCODED]); + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it.todo('should fail to encode value [missing property]', async () => { + const value = { a: [true] }; + + await expectToThrowFuelError( + () => fn.encodeOutput(value), + new FuelError(FuelError.CODES.ENCODE_ERROR, 'Types/values length mismatch.') + ); + }); + }); + + describe('decode', () => { + it('should decode value [{ a: [true, U64_MAX] }]', () => { + const value = concat([BOOL_TRUE_ENCODED, U64_MAX_ENCODED]); + // @ts-expect-error toEqualBn is not a function + const expected = { a: [true, expect.toEqualBn(U64_MAX)] }; + + const actual = fn.decodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it.todo('should fail to encode value [missing property]', async () => { + const value = concat([BOOL_TRUE_ENCODED]); + + await expectToThrowFuelError( + () => fn.decodeOutput(value), + new FuelError(FuelError.CODES.DECODE_ERROR, 'Types/values length mismatch.') + ); + }); + + it.todo('should fail to decode value [additional property]', async () => { + const value = concat([BOOL_FALSE_ENCODED, U32_MIN_ENCODED, toBytes('gûl')]); + + await expectToThrowFuelError( + () => fn.decodeOutput(value), + new FuelError(FuelError.CODES.DECODE_ERROR, 'Types/values length mismatch.') + ); + }); + }); +}); + +describe.only('types_struct_with_implicit_generics', () => { + const fn = contract.functions.types_struct_with_implicit_generics; + + describe('encode', () => { + it('should encode value [{ a: [B256, B256, B256], b: [B256, U8_MAX] }]', () => { + const value = { + a: [B256_DECODED, B256_DECODED, B256_DECODED], + b: [B256_DECODED, U8_MAX], + }; + const expected = concat([ + B256_ENCODED, + B256_ENCODED, + B256_ENCODED, + B256_ENCODED, + U8_MAX_ENCODED, + ]); + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + }); + + describe('decode', () => { + it('should decode value [{ a: [B256, B256, B256], b: [B256, U8_MAX] }]', () => { + const value = concat([ + B256_ENCODED, + B256_ENCODED, + B256_ENCODED, + B256_ENCODED, + U8_MAX_ENCODED, + ]); + const expected = { + a: [B256_DECODED, B256_DECODED, B256_DECODED], + b: [B256_DECODED, U8_MAX], + }; + + const actual = fn.decodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + }); +}); + +describe('types_struct_with_vector', () => { + const fn = contract.functions.types_struct_with_vector; + + describe('encode', () => { + it('should encode value [{ a: 7, b: [3, 9, 6, 4] }]', () => { + const value = { a: U8_MAX, b: [3, 9, 6, 4] }; + const expected = concat([ + U8_MAX_ENCODED, + + // Vector length + Uint8Array.from([0, 0, 0, 0, 0, 0, 0, 4]), + Uint8Array.from([3, 9, 6, 4]), + ]); + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + }); + + describe('decode', () => { + it('should decode value [{ a: 7, b: [3, 9, 6, 4] }]', () => { + const value = concat([ + U8_MAX_ENCODED, + + // Vector length + Uint8Array.from([0, 0, 0, 0, 0, 0, 0, 4]), + Uint8Array.from([3, 9, 6, 4]), + ]); + const expected = { a: U8_MAX, b: [3, 9, 6, 4] }; + + const actual = fn.decodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + }); +}); + +/** + { + fn: exhaustiveExamplesInterface.functions.types_struct_with_vector, + title: '[vector] vector inside struct [with offset]', + value: [ + { + num: 7, + vec: [3, 9, 6, 4], + }, + ], + encodedValue: (input?: any, _offset: number = 0) => { + // eslint-disable-next-line no-param-reassign + input = input[0]; + const u8 = Uint8Array.from([7]); + const length = Uint8Array.from([0, 0, 0, 0, 0, 0, 0, input.vec.length]); + const vectorData = Uint8Array.from(input.vec); + + const expectedBytes = concat([u8, length, vectorData]); + + return expectedBytes; + }, + offset: 16, + }, + */ diff --git a/packages/fuel-gauge/src/abi/encoding/tuple.test.ts b/packages/fuel-gauge/src/abi/encoding/tuple.test.ts new file mode 100644 index 00000000000..31012957591 --- /dev/null +++ b/packages/fuel-gauge/src/abi/encoding/tuple.test.ts @@ -0,0 +1,64 @@ +import type { AbiSpecificationV1 } from '@fuel-ts/abi'; +import { AbiCoder } from '@fuel-ts/abi'; +import { concat } from 'fuels'; + +import { U64_MAX, U64_MAX_ENCODED, U8_MAX, U8_MAX_ENCODED } from '../constants'; +import { AbiProjectsEnum, getAbiForcProject } from '../utils'; + +const { abiContents: contractAbi } = getAbiForcProject(AbiProjectsEnum.ABI_CONTRACT); +const contract = AbiCoder.fromAbi(contractAbi as AbiSpecificationV1); + +describe('types_tuple', () => { + const fn = contract.functions.types_tuple; + + describe('encode', () => { + it('should encode value [U8_MAX, U8_MAX, U8_MAX]', () => { + const value = [U8_MAX, U8_MAX, U8_MAX]; + const expected = concat([U8_MAX_ENCODED, U8_MAX_ENCODED, U8_MAX_ENCODED]); + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + }); + + describe('decode', () => { + it('should decode value [U8_MAX, { propA1: U64_MAX, propA2: "aaa" }]', () => { + const value = concat([U8_MAX_ENCODED, U8_MAX_ENCODED, U8_MAX_ENCODED]); + const expected = [U8_MAX, U8_MAX, U8_MAX]; + + const actual = fn.decodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + }); +}); + +describe('types_tuple_complex', () => { + const fn = contract.functions.types_tuple_complex; + + describe('encode', () => { + it('should encode value [U8_MAX, { a: { a: U64_MAX } }, "aaa"]]', () => { + const value = [U8_MAX, { a: { a: U64_MAX } }, 'aaa']; + const expected = concat([U8_MAX_ENCODED, U64_MAX_ENCODED, Uint8Array.from([97, 97, 97])]); + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + }); + + describe('decode', () => { + it('should decode value [[U8_MAX, { propA1: U64_MAX, propA2: "aaa" }]]', () => { + const value = concat([U8_MAX_ENCODED, U64_MAX_ENCODED, Uint8Array.from([97, 97, 97])]); + const expected = [U8_MAX, { a: { a: U64_MAX } }, 'aaa']; + + const actual = fn.decodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + }); +}); + +describe.todo('types_tuple_with_native_types'); +describe.todo('types_tuple_alias_with_native_types'); diff --git a/packages/fuel-gauge/src/abi/encoding/u16.test.ts b/packages/fuel-gauge/src/abi/encoding/u16.test.ts new file mode 100644 index 00000000000..3218343fb8c --- /dev/null +++ b/packages/fuel-gauge/src/abi/encoding/u16.test.ts @@ -0,0 +1,112 @@ +import type { AbiSpecificationV1 } from '@fuel-ts/abi'; +import { AbiCoder } from '@fuel-ts/abi'; +import { FuelError } from 'fuels'; +import { expectToThrowFuelError } from 'fuels/test-utils'; + +import { U16_MAX, U16_MAX_ENCODED, U16_MIN, U16_MIN_ENCODED, U8_MAX } from '../constants'; +import { AbiProjectsEnum, getAbiForcProject } from '../utils'; + +const { abiContents: contractAbi } = getAbiForcProject(AbiProjectsEnum.ABI_CONTRACT); +const contract = AbiCoder.fromAbi(contractAbi as AbiSpecificationV1); + +describe('types_u16', () => { + const fn = contract.functions.types_u16; + const U16_MIN_MINUS_ONE = U16_MIN - 1; + const U16_MIN_MINUS_ONE_ENCODED = new Uint8Array([U16_MIN_MINUS_ONE]); + const U16_MAX_PLUS_ONE = U16_MAX + 1; + const U16_MAX_PLUS_ONE_ENCODED = new Uint8Array([U16_MAX_PLUS_ONE]); + + describe('encode', () => { + it('should encode value [min]', () => { + const value = U16_MIN; + const expected = U16_MIN_ENCODED; + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it('should encode value [max]', () => { + const value = U16_MAX; + const expected = U16_MAX_ENCODED; + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it('should encode value [u8]', () => { + const value = U8_MAX; + const expected = new Uint8Array([0, U8_MAX]); + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it.todo('should fail to encode value [min - 1]', async () => { + const value = U16_MIN_MINUS_ONE; + + await expectToThrowFuelError( + () => fn.encodeOutput(value), + new FuelError(FuelError.CODES.ENCODE_ERROR, 'Invalid U16 value.') + ); + }); + + it.todo('should fail to encode value [max + 1]', async () => { + const value = U16_MAX_PLUS_ONE; + + await expectToThrowFuelError( + () => fn.encodeOutput(value), + new FuelError(FuelError.CODES.ENCODE_ERROR, 'Invalid U16 value.') + ); + }); + }); + + describe('decode', () => { + it('should decode value [min]', () => { + const value = U16_MIN_ENCODED; + const expected = U16_MIN; + + const actual = fn.decodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it('should decode value [max]', () => { + const value = U16_MAX_ENCODED; + const expected = U16_MAX; + + const actual = fn.decodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it('should decode value [u8]', () => { + const value = new Uint8Array([0, U8_MAX]); + const expected = U8_MAX; + + const actual = fn.decodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it.todo('should fail to decode value [min - 1]', async () => { + const value = U16_MIN_MINUS_ONE_ENCODED; + + await expectToThrowFuelError( + () => fn.decodeOutput(value), + new FuelError(FuelError.CODES.ENCODE_ERROR, 'Invalid u16 value.') + ); + }); + + it.todo('should fail to decode value [max + 1]', async () => { + const value = U16_MAX_PLUS_ONE_ENCODED; + + await expectToThrowFuelError( + () => fn.decodeOutput(value), + new FuelError(FuelError.CODES.DECODE_ERROR, 'Invalid u16 value.') + ); + }); + }); +}); diff --git a/packages/fuel-gauge/src/abi/encoding/u256.test.ts b/packages/fuel-gauge/src/abi/encoding/u256.test.ts new file mode 100644 index 00000000000..22d6ac98aa6 --- /dev/null +++ b/packages/fuel-gauge/src/abi/encoding/u256.test.ts @@ -0,0 +1,99 @@ +import type { AbiSpecificationV1 } from '@fuel-ts/abi'; +import { AbiCoder } from '@fuel-ts/abi'; +import { FuelError } from 'fuels'; +import { expectToThrowFuelError } from 'fuels/test-utils'; + +import { U256_MAX, U256_MAX_ENCODED, U256_MIN, U256_MIN_ENCODED } from '../constants'; +import { AbiProjectsEnum, getAbiForcProject } from '../utils'; +import { toEqualBn } from '../vitest.matcher'; + +// @TODO how do we enable this matcher globally? +expect.extend({ toEqualBn }); + +const { abiContents: contractAbi } = getAbiForcProject(AbiProjectsEnum.ABI_CONTRACT); +const contract = AbiCoder.fromAbi(contractAbi as AbiSpecificationV1); + +describe('types_u256', () => { + const fn = contract.functions.types_u256; + const U256_MIN_MINUS_ONE = U256_MIN - 1; + const U256_MIN_MINUS_ONE_ENCODED = new Uint8Array([U256_MIN_MINUS_ONE]); + const U256_MAX_PLUS_ONE = U256_MAX.add(1); + const U256_MAX_PLUS_ONE_ENCODED = U256_MAX_PLUS_ONE.toBytes(); + + describe('encode', () => { + it('should encode value [min]', () => { + const value = U256_MIN; + const expected = U256_MIN_ENCODED; + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it('should encode value [max]', () => { + const value = U256_MAX; + const expected = U256_MAX_ENCODED; + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it.todo('should fail to encode value [min - 1]', async () => { + const value = U256_MIN_MINUS_ONE; + + await expectToThrowFuelError( + () => fn.encodeOutput(value), + new FuelError(FuelError.CODES.ENCODE_ERROR, 'Invalid U256 value.') + ); + }); + + it.todo('should fail to encode value [max + 1]', async () => { + const value = U256_MAX_PLUS_ONE; + + await expectToThrowFuelError( + () => fn.encodeOutput(value), + new FuelError(FuelError.CODES.ENCODE_ERROR, 'Invalid U256 value.') + ); + }); + }); + + describe('decode', () => { + it('should decode value [min]', () => { + const value = U256_MIN_ENCODED; + const expected = U256_MIN; + + const actual = fn.decodeOutput(value); + + // @ts-expect-error toEqualBn is not a function + expect(actual).toStrictEqual(expect.toEqualBn(expected)); + }); + + it('should decode value [max]', () => { + const value = U256_MAX_ENCODED; + const expected = U256_MAX; + + const actual = fn.decodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it.todo('should fail to decode value [min - 1]', async () => { + const value = U256_MIN_MINUS_ONE_ENCODED; + + await expectToThrowFuelError( + () => fn.decodeOutput(value), + new FuelError(FuelError.CODES.ENCODE_ERROR, 'Invalid u256 value.') + ); + }); + + it.todo('should fail to decode value [max + 1]', async () => { + const value = U256_MAX_PLUS_ONE_ENCODED; + + await expectToThrowFuelError( + () => fn.decodeOutput(value), + new FuelError(FuelError.CODES.DECODE_ERROR, 'Invalid u256 value.') + ); + }); + }); +}); diff --git a/packages/fuel-gauge/src/abi/encoding/u32.test.ts b/packages/fuel-gauge/src/abi/encoding/u32.test.ts new file mode 100644 index 00000000000..b7b62d0d2a1 --- /dev/null +++ b/packages/fuel-gauge/src/abi/encoding/u32.test.ts @@ -0,0 +1,130 @@ +import type { AbiSpecificationV1 } from '@fuel-ts/abi'; +import { AbiCoder } from '@fuel-ts/abi'; +import { FuelError } from 'fuels'; +import { expectToThrowFuelError } from 'fuels/test-utils'; + +import { U16_MAX, U32_MAX, U32_MAX_ENCODED, U32_MIN, U32_MIN_ENCODED, U8_MAX } from '../constants'; +import { AbiProjectsEnum, getAbiForcProject } from '../utils'; + +const { abiContents: contractAbi } = getAbiForcProject(AbiProjectsEnum.ABI_CONTRACT); +const contract = AbiCoder.fromAbi(contractAbi as AbiSpecificationV1); + +describe('types_u32', () => { + const fn = contract.functions.types_u32; + const U32_MIN_MINUS_ONE = U32_MIN - 1; + const U32_MIN_MINUS_ONE_ENCODED = new Uint8Array([U32_MIN_MINUS_ONE]); + const U32_MAX_PLUS_ONE = U32_MAX + 1; + const U32_MAX_PLUS_ONE_ENCODED = new Uint8Array([U32_MAX_PLUS_ONE]); + + describe('encode', () => { + it('should encode value [min]', () => { + const value = U32_MIN; + const expected = U32_MIN_ENCODED; + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it('should encode value [max]', () => { + const value = U32_MAX; + const expected = U32_MAX_ENCODED; + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it('should encode value [u8]', () => { + const value = U8_MAX; + const expected = new Uint8Array([0, 0, 0, U8_MAX]); + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it('should encode value [u16]', () => { + const value = U16_MAX; + const expected = new Uint8Array([0, 0, U8_MAX, U8_MAX]); + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it.todo('should fail to encode value [min - 1]', async () => { + const value = U32_MIN_MINUS_ONE; + + await expectToThrowFuelError( + () => fn.encodeOutput(value), + new FuelError(FuelError.CODES.ENCODE_ERROR, 'Invalid U32 value.') + ); + }); + + it.todo('should fail to encode value [max + 1]', async () => { + const value = U32_MAX_PLUS_ONE; + + await expectToThrowFuelError( + () => fn.encodeOutput(value), + new FuelError(FuelError.CODES.ENCODE_ERROR, 'Invalid U32 value.') + ); + }); + }); + + describe('decode', () => { + it('should decode value [min]', () => { + const value = U32_MIN_ENCODED; + const expected = U32_MIN; + + const actual = fn.decodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it('should decode value [max]', () => { + const value = U32_MAX_ENCODED; + const expected = U32_MAX; + + const actual = fn.decodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it('should decode value [u8]', () => { + const value = new Uint8Array([0, 0, 0, U8_MAX]); + const expected = U8_MAX; + + const actual = fn.decodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it('should decode value [u16]', () => { + const value = new Uint8Array([0, 0, U8_MAX, U8_MAX]); + const expected = U16_MAX; + + const actual = fn.decodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it.todo('should fail to decode value [min - 1]', async () => { + const value = U32_MIN_MINUS_ONE_ENCODED; + + await expectToThrowFuelError( + () => fn.decodeOutput(value), + new FuelError(FuelError.CODES.ENCODE_ERROR, 'Invalid u32 value.') + ); + }); + + it.todo('should fail to decode value [max + 1]', async () => { + const value = U32_MAX_PLUS_ONE_ENCODED; + + await expectToThrowFuelError( + () => fn.decodeOutput(value), + new FuelError(FuelError.CODES.DECODE_ERROR, 'Invalid u32 value.') + ); + }); + }); +}); diff --git a/packages/fuel-gauge/src/abi/encoding/u64.test.ts b/packages/fuel-gauge/src/abi/encoding/u64.test.ts new file mode 100644 index 00000000000..f042bf69fe2 --- /dev/null +++ b/packages/fuel-gauge/src/abi/encoding/u64.test.ts @@ -0,0 +1,165 @@ +import type { AbiSpecificationV1 } from '@fuel-ts/abi'; +import { AbiCoder } from '@fuel-ts/abi'; +import { FuelError } from 'fuels'; +import { expectToThrowFuelError } from 'fuels/test-utils'; + +import { + U16_MAX, + U32_MAX, + U64_MAX, + U64_MAX_ENCODED, + U64_MIN, + U64_MIN_ENCODED, + U8_MAX, +} from '../constants'; +import { AbiProjectsEnum, getAbiForcProject } from '../utils'; +import { toEqualBn } from '../vitest.matcher'; + +// @TODO how do we enable this matcher globally? +expect.extend({ toEqualBn }); + +const { abiContents: contractAbi } = getAbiForcProject(AbiProjectsEnum.ABI_CONTRACT); +const contract = AbiCoder.fromAbi(contractAbi as AbiSpecificationV1); + +describe('types_u64', () => { + const fn = contract.functions.types_u64; + const U64_MIN_MINUS_ONE = U64_MIN - 1; + const U64_MIN_MINUS_ONE_ENCODED = new Uint8Array([U64_MIN_MINUS_ONE]); + const U64_MAX_PLUS_ONE = U64_MAX.add(1); + const U64_MAX_PLUS_ONE_ENCODED = U64_MAX_PLUS_ONE.toBytes(); + + describe('encode', () => { + it('should encode value [min]', () => { + const value = U64_MIN; + const expected = U64_MIN_ENCODED; + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it('should encode value [max]', () => { + const value = U64_MAX; + const expected = U64_MAX_ENCODED; + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it('should encode value [u8]', () => { + const value = U8_MAX; + const expected = new Uint8Array([0, 0, 0, 0, 0, 0, 0, U8_MAX]); + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it('should encode value [u16]', () => { + const value = U16_MAX; + const expected = new Uint8Array([0, 0, 0, 0, 0, 0, U8_MAX, U8_MAX]); + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it('should encode value [u32]', () => { + const value = U32_MAX; + const expected = new Uint8Array([0, 0, 0, 0, U8_MAX, U8_MAX, U8_MAX, U8_MAX]); + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it.todo('should fail to encode value [min - 1]', async () => { + const value = U64_MIN_MINUS_ONE; + + await expectToThrowFuelError( + () => fn.encodeOutput(value), + new FuelError(FuelError.CODES.ENCODE_ERROR, 'Invalid U64 value.') + ); + }); + + it.todo('should fail to encode value [max + 1]', async () => { + const value = U64_MAX_PLUS_ONE; + + await expectToThrowFuelError( + () => fn.encodeOutput(value), + new FuelError(FuelError.CODES.ENCODE_ERROR, 'Invalid U64 value.') + ); + }); + }); + + describe('decode', () => { + it('should decode value [min]', () => { + const value = U64_MIN_ENCODED; + const expected = U64_MIN; + + const actual = fn.decodeOutput(value); + + // @ts-expect-error toEqualBn is not a function + expect(actual).toStrictEqual(expect.toEqualBn(expected)); + }); + + it('should decode value [max]', () => { + const value = U64_MAX_ENCODED; + const expected = U64_MAX; + + const actual = fn.decodeOutput(value); + + // @ts-expect-error toEqualBn is not a function + expect(actual).toStrictEqual(expect.toEqualBn(expected)); + }); + + it('should decode value [u8]', () => { + const value = new Uint8Array([0, 0, 0, 0, 0, 0, 0, U8_MAX]); + const expected = U8_MAX; + + const actual = fn.decodeOutput(value); + + // @ts-expect-error toEqualBn is not a function + expect(actual).toStrictEqual(expect.toEqualBn(expected)); + }); + + it('should decode value [u16]', () => { + const value = new Uint8Array([0, 0, 0, 0, 0, 0, U8_MAX, U8_MAX]); + const expected = U16_MAX; + + const actual = fn.decodeOutput(value); + + // @ts-expect-error toEqualBn is not a function + expect(actual).toStrictEqual(expect.toEqualBn(expected)); + }); + + it('should decode value [u32]', () => { + const value = new Uint8Array([0, 0, 0, 0, U8_MAX, U8_MAX, U8_MAX, U8_MAX]); + const expected = U32_MAX; + + const actual = fn.decodeOutput(value); + + // @ts-expect-error toEqualBn is not a function + expect(actual).toStrictEqual(expect.toEqualBn(expected)); + }); + + it.todo('should fail to decode value [min - 1]', async () => { + const value = U64_MIN_MINUS_ONE_ENCODED; + + await expectToThrowFuelError( + () => fn.decodeOutput(value), + new FuelError(FuelError.CODES.ENCODE_ERROR, 'Invalid u64 value.') + ); + }); + + it.todo('should fail to decode value [max + 1]', async () => { + const value = U64_MAX_PLUS_ONE_ENCODED; + + await expectToThrowFuelError( + () => fn.decodeOutput(value), + new FuelError(FuelError.CODES.DECODE_ERROR, 'Invalid u64 value.') + ); + }); + }); +}); diff --git a/packages/fuel-gauge/src/abi/encoding/u8.test.ts b/packages/fuel-gauge/src/abi/encoding/u8.test.ts new file mode 100644 index 00000000000..41edff5f269 --- /dev/null +++ b/packages/fuel-gauge/src/abi/encoding/u8.test.ts @@ -0,0 +1,112 @@ +import type { AbiSpecificationV1 } from '@fuel-ts/abi'; +import { AbiCoder } from '@fuel-ts/abi'; +import { FuelError } from 'fuels'; +import { expectToThrowFuelError } from 'fuels/test-utils'; + +import { U8_MAX, U8_MAX_ENCODED, U8_MIN, U8_MIN_ENCODED } from '../constants'; +import { AbiProjectsEnum, getAbiForcProject } from '../utils'; + +const { abiContents: contractAbi } = getAbiForcProject(AbiProjectsEnum.ABI_CONTRACT); +const contract = AbiCoder.fromAbi(contractAbi as AbiSpecificationV1); + +describe('types_u8', () => { + const fn = contract.functions.types_u8; + const U8_MIN_MINUS_ONE = U8_MIN - 1; + const U8_MIN_MINUS_ONE_ENCODED = new Uint8Array([U8_MIN_MINUS_ONE]); + const U8_MAX_PLUS_ONE = U8_MAX + 1; + const U8_MAX_PLUS_ONE_ENCODED = new Uint8Array([U8_MAX_PLUS_ONE]); + + describe('encode', () => { + it('should encode value [min]', () => { + const value = U8_MIN; + const expected = U8_MIN_ENCODED; + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it('should encode value [max]', () => { + const value = U8_MAX; + const expected = U8_MAX_ENCODED; + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it('should encode value [u8]', () => { + const value = U8_MAX; + const expected = new Uint8Array([U8_MAX]); + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it.todo('should fail to encode value [min - 1]', async () => { + const value = U8_MAX; + + await expectToThrowFuelError( + () => fn.encodeOutput(value), + new FuelError(FuelError.CODES.ENCODE_ERROR, 'Invalid u8 value.') + ); + }); + + it.todo('should fail to encode value [max + 1]', async () => { + const value = U8_MAX; + + await expectToThrowFuelError( + () => fn.encodeOutput(value), + new FuelError(FuelError.CODES.ENCODE_ERROR, 'Invalid u8 value.') + ); + }); + }); + + describe('decode', () => { + it('should decode value [min]', () => { + const value = U8_MIN_ENCODED; + const expected = U8_MIN; + + const actual = fn.decodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it('should decode value [max]', () => { + const value = U8_MIN_ENCODED; + const expected = U8_MIN; + + const actual = fn.decodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it('should decode value [u8]', () => { + const value = new Uint8Array([U8_MAX]); + const expected = U8_MAX; + + const actual = fn.decodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + + it.todo('should fail to decode value [min - 1]', async () => { + const value = U8_MIN_MINUS_ONE_ENCODED; + + await expectToThrowFuelError( + () => fn.decodeOutput(value), + new FuelError(FuelError.CODES.ENCODE_ERROR, 'Invalid u8 value.') + ); + }); + + it.todo('should fail to decode value [max + 1]', async () => { + const value = U8_MAX_PLUS_ONE_ENCODED; + + await expectToThrowFuelError( + () => fn.decodeOutput(value), + new FuelError(FuelError.CODES.ENCODE_ERROR, 'Invalid u8 value.') + ); + }); + }); +}); diff --git a/packages/fuel-gauge/src/abi/encoding/vector.test.ts b/packages/fuel-gauge/src/abi/encoding/vector.test.ts new file mode 100644 index 00000000000..7f7370d5c14 --- /dev/null +++ b/packages/fuel-gauge/src/abi/encoding/vector.test.ts @@ -0,0 +1,127 @@ +import type { AbiSpecificationV1 } from '@fuel-ts/abi'; +import { AbiCoder } from '@fuel-ts/abi'; +import { concat } from 'fuels'; + +import { U8_MAX } from '../constants'; +import { AbiProjectsEnum, getAbiForcProject } from '../utils'; + +const { abiContents: contractAbi } = getAbiForcProject(AbiProjectsEnum.ABI_CONTRACT); +const contract = AbiCoder.fromAbi(contractAbi as AbiSpecificationV1); + +describe('types_vector_boolean', () => { + const fn = contract.functions.types_vector_boolean; + + describe('encode', () => { + it('should encode value [[true, false, true, true]]', () => { + const value: boolean[] = [true, false, true, true]; + const expected = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 4, 1, 0, 1, 1]); + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + }); + + describe('decode', () => { + it('should decode value [[true, false, true, true]]', () => { + const value = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 4, 1, 0, 1, 1]); + const expected: boolean[] = [true, false, true, true]; + + const actual = fn.decodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + }); +}); + +describe('types_vector_u8', () => { + const fn = contract.functions.types_vector_u8; + + describe('encode', () => { + it('should encode value [[U8_MAX, 0, U8_MAX, U8_MAX]]', () => { + const value: number[] = [U8_MAX, 0, U8_MAX, U8_MAX]; + const expected = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 4, U8_MAX, 0, U8_MAX, U8_MAX]); + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + }); + + describe('decode', () => { + it('should decode value [[true, false, true, true]]', () => { + const value = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 4, U8_MAX, 0, U8_MAX, U8_MAX]); + const expected: number[] = [U8_MAX, 0, U8_MAX, U8_MAX]; + + const actual = fn.decodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + }); +}); + +describe('types_vector_inside_vector', () => { + const fn = contract.functions.types_vector_inside_vector; + + describe('encode', () => { + it('should encode value [[0, 1, 2], [6, 7, 8]]', () => { + const value: number[][] = [ + [0, 1, 2], + [6, 7, 8], + ]; + const expected = concat([ + // Wrapper vector length + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 2]), + + // First vector length + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 3]), + // First vector + new Uint8Array([0, 0, 0, 0]), + new Uint8Array([0, 0, 0, 1]), + new Uint8Array([0, 0, 0, 2]), + + // Second vector length + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 3]), + // Second vector + new Uint8Array([0, 0, 0, 6]), + new Uint8Array([0, 0, 0, 7]), + new Uint8Array([0, 0, 0, 8]), + ]); + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + }); + + describe('decode', () => { + it('should decode value [[0, 1, 2], [6, 7, 8]]', () => { + const value = concat([ + // Wrapper vector length + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 2]), + + // First vector length + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 3]), + // First vector + new Uint8Array([0, 0, 0, 0]), + new Uint8Array([0, 0, 0, 1]), + new Uint8Array([0, 0, 0, 2]), + + // Second vector length + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 3]), + // Second vector + new Uint8Array([0, 0, 0, 6]), + new Uint8Array([0, 0, 0, 7]), + new Uint8Array([0, 0, 0, 8]), + ]); + const expected: number[][] = [ + [0, 1, 2], + [6, 7, 8], + ]; + + const actual = fn.decodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + }); +}); diff --git a/packages/fuel-gauge/src/abi/encoding/void.test.ts b/packages/fuel-gauge/src/abi/encoding/void.test.ts new file mode 100644 index 00000000000..3ff063fd450 --- /dev/null +++ b/packages/fuel-gauge/src/abi/encoding/void.test.ts @@ -0,0 +1,32 @@ +import type { AbiSpecificationV1 } from '@fuel-ts/abi'; +import { AbiCoder } from '@fuel-ts/abi'; + +import { AbiProjectsEnum, getAbiForcProject } from '../utils'; + +const { abiContents: contractAbi } = getAbiForcProject(AbiProjectsEnum.ABI_CONTRACT); +const contract = AbiCoder.fromAbi(contractAbi as AbiSpecificationV1); + +describe('types_void', () => { + const fn = contract.functions.types_void; + + describe('encode', () => { + it('should encode value [undefined]', () => { + const value: undefined = undefined; + const expected = new Uint8Array([]); + + const actual = fn.encodeOutput(value); + + expect(actual).toStrictEqual(expected); + }); + }); + + describe('decode', () => { + it('should decode value', () => { + const value = new Uint8Array([]); + + const actual = fn.decodeOutput(value); + + expect(actual).toStrictEqual(undefined); + }); + }); +}); diff --git a/packages/fuel-gauge/src/abi/utils.ts b/packages/fuel-gauge/src/abi/utils.ts index cc08b4ff954..8f6a03955b8 100644 --- a/packages/fuel-gauge/src/abi/utils.ts +++ b/packages/fuel-gauge/src/abi/utils.ts @@ -5,6 +5,7 @@ export enum AbiProjectsEnum { ABI_CONTRACT = 'abi-contract', ABI_PREDICATE = 'abi-predicate', ABI_SCRIPT = 'abi-script', + ABI_EXHAUSTIVE_EXAMPLES = 'abi-exhaustive-examples', } const forcProjectsDir = join(__dirname, '../../test/fixtures/forc-projects'); diff --git a/packages/fuel-gauge/src/blob-deploy.test.ts b/packages/fuel-gauge/src/blob-deploy.test.ts index 9bbcd328d66..a1274e22dab 100644 --- a/packages/fuel-gauge/src/blob-deploy.test.ts +++ b/packages/fuel-gauge/src/blob-deploy.test.ts @@ -22,9 +22,10 @@ describe('deploying blobs', () => { ): Record { const configurables: Record = {}; - Object.entries(program.interface.configurables).forEach(([key, { offset, concreteTypeId }]) => { + Object.entries(program.interface.configurables).forEach(([key, { offset }]) => { const data = program.bytes.slice(offset); - configurables[key] = program.interface.decodeType(concreteTypeId, data)[0]; + const coder = program.interface.getConfigurable(key); + configurables[key] = coder.decode(data); }); return configurables; @@ -236,7 +237,7 @@ describe('deploying blobs', () => { const predicate = new Predicate({ data: [configurable.FEE, configurable.ADDRESS], bytecode: loaderPredicate.bytes, - abi: loaderPredicate.interface.jsonAbi, + abi: loaderPredicate.interface.specification, provider, configurableConstants: configurable, }); diff --git a/packages/fuel-gauge/src/contract-factory.test.ts b/packages/fuel-gauge/src/contract-factory.test.ts index 8cb8300385c..7fb9388513a 100644 --- a/packages/fuel-gauge/src/contract-factory.test.ts +++ b/packages/fuel-gauge/src/contract-factory.test.ts @@ -1,7 +1,7 @@ import type { Account, TransactionResult } from '@fuel-ts/account'; import { FuelError, ErrorCode } from '@fuel-ts/errors'; import { expectToThrowFuelError } from '@fuel-ts/errors/test-utils'; -import { BN, bn, toHex, Interface, ContractFactory, arrayify, concat } from 'fuels'; +import { BN, bn, toHex, AbiCoder, ContractFactory, arrayify, concat } from 'fuels'; import { launchTestNode } from 'fuels/test-utils'; import { @@ -25,7 +25,7 @@ describe('Contract Factory', () => { using contract = await launchTestContract({ factory: StorageTestContractFactory, }); - expect(contract.interface).toBeInstanceOf(Interface); + expect(contract.interface).toBeInstanceOf(AbiCoder); const { waitForResult } = await contract.functions.initialize_counter(41).call(); const { value: valueInitial } = await waitForResult(); @@ -46,7 +46,7 @@ describe('Contract Factory', () => { factory: StorageTestContractFactory, }); - expect(contract.interface).toBeInstanceOf(Interface); + expect(contract.interface).toBeInstanceOf(AbiCoder); const call1 = await contract.functions.initialize_counter(100).call(); await call1.waitForResult(); diff --git a/packages/fuel-gauge/src/mapped-error-messages.test.ts b/packages/fuel-gauge/src/mapped-error-messages.test.ts index d2a1404a8e7..861fcbe738f 100644 --- a/packages/fuel-gauge/src/mapped-error-messages.test.ts +++ b/packages/fuel-gauge/src/mapped-error-messages.test.ts @@ -15,7 +15,11 @@ describe('mapped error messages', () => { const emptyWallet = Wallet.generate({ provider: contract.provider }); - const emptyWalletContract = new Contract(contract.id, contract.interface.jsonAbi, emptyWallet); + const emptyWalletContract = new Contract( + contract.id, + contract.interface.specification, + emptyWallet + ); await expectToThrowFuelError(() => emptyWalletContract.functions.return_void().call(), { code: ErrorCode.NOT_ENOUGH_FUNDS, diff --git a/packages/fuel-gauge/src/min-gas.test.ts b/packages/fuel-gauge/src/min-gas.test.ts index 7eb0cfa99a7..936be3940d9 100644 --- a/packages/fuel-gauge/src/min-gas.test.ts +++ b/packages/fuel-gauge/src/min-gas.test.ts @@ -5,9 +5,9 @@ import { Address, hexlify, getGasUsedFromReceipts, - BigNumberCoder, ContractFactory, GAS_USED_MODIFIER, + AbiEncoding, } from 'fuels'; import { launchTestNode } from 'fuels/test-utils'; @@ -83,7 +83,7 @@ describe('Minimum gas tests', () => { const request = new ScriptTransactionRequest({ script: ComplexScript.bytecode, - scriptData: hexlify(new BigNumberCoder('u64').encode(bn(2000))), + scriptData: hexlify(AbiEncoding.v1.coders.u64.encode(bn(2000))), }); request.addCoinOutput(Address.fromRandom(), bn(100), provider.getBaseAssetId()); @@ -187,7 +187,7 @@ describe('Minimum gas tests', () => { */ const request = new ScriptTransactionRequest({ script: ComplexScript.bytecode, - scriptData: hexlify(new BigNumberCoder('u64').encode(bn(2000))), + scriptData: hexlify(AbiEncoding.v1.coders.u64.encode(bn(2000))), }); // add predicate transfer diff --git a/packages/fuel-gauge/test/fixtures/forc-projects/Forc.toml b/packages/fuel-gauge/test/fixtures/forc-projects/Forc.toml index f4f48fc715c..0c656a9f286 100644 --- a/packages/fuel-gauge/test/fixtures/forc-projects/Forc.toml +++ b/packages/fuel-gauge/test/fixtures/forc-projects/Forc.toml @@ -1,8 +1,6 @@ [workspace] members = [ "abi-contract", - "abi-script", - "abi-predicate", "advanced-logging", "advanced-logging-abi", "advanced-logging-other-contract", diff --git a/packages/fuel-gauge/test/fixtures/forc-projects/abi-contract/src/main.sw b/packages/fuel-gauge/test/fixtures/forc-projects/abi-contract/src/main.sw index afc7ebb063f..11d0c9fd4dd 100644 --- a/packages/fuel-gauge/test/fixtures/forc-projects/abi-contract/src/main.sw +++ b/packages/fuel-gauge/test/fixtures/forc-projects/abi-contract/src/main.sw @@ -26,6 +26,23 @@ fn divide(numerator: u64, denominator: u64) -> Result { abi AbiContract { fn configurables() -> Configurables; + fn attributes_none() -> (); + #[storage(read)] + fn attributes_storage_read() -> (); + #[storage(write)] + fn attributes_storage_write() -> (); + #[storage(read, write)] + fn attributes_storage_read_write() -> (); + #[payable] + fn attributes_payable() -> (); + #[test] + fn attributes_test() -> (); + #[inline(never)] + fn attributes_inline_never() -> (); + #[inline(always)] + fn attributes_inline_always() -> (); + fn attributes_doc_comment() -> (); + fn types_u8(x: u8) -> u8; fn types_u16(x: u16) -> u16; fn types_u32(x: u32) -> u32; @@ -145,6 +162,51 @@ impl AbiContract for Contract { } } + fn attributes_none() -> () { + () + } + + #[storage(read)] + fn attributes_storage_read() -> () { + () + } + + #[storage(write)] + fn attributes_storage_write() -> () { + () + } + + #[storage(read, write)] + fn attributes_storage_read_write() -> () { + () + } + + #[payable] + fn attributes_payable() -> () { + () + } + + #[test] + fn attributes_test() -> () { + () + } + + #[inline(never)] + fn attributes_inline_never() -> () { + () + } + + #[inline(always)] + fn attributes_inline_always() -> () { + () + } + + /// This is a doc + /// This is another doc comment + fn attributes_doc_comment() -> () { + () + } + fn types_u8(x: u8) -> u8 { assert_eq(x, 8); diff --git a/packages/fuel-gauge/test/fixtures/forc-projects/abi-exhaustive-examples/.gitignore b/packages/fuel-gauge/test/fixtures/forc-projects/abi-exhaustive-examples/.gitignore new file mode 100644 index 00000000000..77d3844f58c --- /dev/null +++ b/packages/fuel-gauge/test/fixtures/forc-projects/abi-exhaustive-examples/.gitignore @@ -0,0 +1,2 @@ +out +target diff --git a/packages/fuel-gauge/test/fixtures/forc-projects/abi-exhaustive-examples/Forc.toml b/packages/fuel-gauge/test/fixtures/forc-projects/abi-exhaustive-examples/Forc.toml new file mode 100644 index 00000000000..9f2349d167f --- /dev/null +++ b/packages/fuel-gauge/test/fixtures/forc-projects/abi-exhaustive-examples/Forc.toml @@ -0,0 +1,7 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "abi-exhaustive-examples" + +[dependencies] diff --git a/packages/fuel-gauge/test/fixtures/forc-projects/abi-exhaustive-examples/src/main.sw b/packages/fuel-gauge/test/fixtures/forc-projects/abi-exhaustive-examples/src/main.sw new file mode 100644 index 00000000000..246469c89b2 --- /dev/null +++ b/packages/fuel-gauge/test/fixtures/forc-projects/abi-exhaustive-examples/src/main.sw @@ -0,0 +1,344 @@ +// Sourced from the `abi-coder/exhaustive-examples` project. +// TODO: move all tests into `abi-contract` project. +contract; +use std::b512::B512; +use std::bytes::Bytes; +use std::string::String; + +enum EnumWithGeneric { + VariantOne: A, + VariantTwo: u64, +} + +configurable { + U8: u8 = 8u8, + BOOL: bool = true, + ARRAY: [u32; 3] = [253u32, 254u32, 255u32], + STR_4: str[4] = __to_str_array("fuel"), + STRUCT: StructA = StructA { + propA1: 8u8, + propA2: true, + }, + ENUM: EnumWithGeneric = EnumWithGeneric::VariantOne(true), +} + +struct StructA { + propA1: B, + propA2: C, +} + +struct StructB { + propB1: D, +} + +struct StructC { + propC1: StructA, u16>, +} + +struct SimpleStruct { + a: bool, + b: u32, +} + +enum Color { + Blue: (), + Green: (), + Red: (), + Silver: (), + Grey: (), +} + +enum EnumWithBuiltinType { + a: bool, + b: u64, +} + +enum EnumWithStructs { + a: Color, + b: SimpleStruct, + c: StructA, +} + +struct StructWithImplicitGenerics { + arr: [E; 3], + tuple: (E, F), +} + +enum MyGenericEnum { + Foo: u64, + Bar: bool, +} +struct MyGenericStruct { + bim: G, + bam: MyGenericEnum, +} + +struct MyOtherStruct { + bom: u64, +} + +enum MyEnum { + Foo: u64, + Bar: bool, + Din: bool, +} + +struct MyStructWithEnum { + bim: str[3], + bam: MyEnum, +} +struct MyStruct { + dummy_a: bool, + dummy_b: u64, +} +struct Test { + foo: u64, + bar: u64, +} + +enum TestEnum { + Value: bool, + Data: bool, +} + +enum EnumWithVector { + num: u8, + vec: Vec, +} + +struct StructWithVector { + num: u8, + vec: Vec, +} + +struct MyStructWithGeneric { + bim: I, + bam: StructB, + bom: StructA, +} + +struct ArrWithGenericStruct { + a: [MyStructWithGeneric; 3], +} + +abi MyContract { + fn test_function() -> bool; + fn u_8(arg: u8) -> u8; + fn u_16(arg: u16) -> u16; + fn u_32(arg: u32) -> u32; + fn u_64(arg: u64) -> u64; + fn string(arg: str[5]) -> str[5]; + fn boolean(arg: bool) -> bool; + fn b_256(arg: b256) -> b256; + fn b_512(arg: B512) -> B512; + fn two_args(arg1: b256, arg2: bool) -> (b256, bool); + fn struct_simple(x: SimpleStruct) -> SimpleStruct; + fn struct_generic_simple(x: StructB) -> StructB; + fn struct_with_tuple(x: StructB<(bool, u64)>) -> StructB<(bool, u64)>; + fn struct_with_implicit_generics( + arg: StructWithImplicitGenerics, + ) -> StructWithImplicitGenerics; + fn bytes(arg: Bytes) -> Bytes; + fn raw_slice(arg: raw_slice) -> raw_slice; + fn dynamic_string(arg: String) -> String; + fn asset_id(arg: AssetId) -> AssetId; + + fn tuple_as_param(x: (u8, StructA, str[3]>)) -> (u8, StructA, str[3]>); + fn array_simple(x: [u8; 4]) -> [u8; 4]; + fn array_struct(x: [SimpleStruct; 3]) -> [SimpleStruct; 3]; + fn vector_boolean(x: Vec) -> Vec; + fn vector_u8(x: Vec) -> Vec; + fn arg_then_vector_u8(a: SimpleStruct, x: Vec) -> (SimpleStruct, Vec); + fn vector_u8_then_arg(x: Vec, y: b256) -> (Vec, b256); + fn two_u8_vectors(x: Vec, y: Vec) -> (Vec, Vec); + fn u32_then_three_vectors_u64( + x: u32, + y: Vec, + z: Vec, + q: Vec, + ) -> (u32, Vec, Vec, Vec); + fn enum_simple(x: Color) -> Color; + fn enum_with_builtin_type(x: EnumWithBuiltinType) -> EnumWithBuiltinType; + fn enum_with_structs(x: EnumWithStructs) -> EnumWithStructs; + fn option_u8(x: Option) -> Option; + fn return_configurables() -> (u8, bool, [u32; 3], str[4], StructA); + fn simple_vector(arg: Vec) -> Vec; + fn vector_inside_vector(arg: Vec>) -> Vec>; + fn vector_inside_array(arg: [Vec; 1]) -> [Vec; 1]; + fn vector_inside_enum(arg: EnumWithVector) -> EnumWithVector; + fn vector_inside_struct(arg: StructWithVector) -> StructWithVector; + fn array_with_generic_struct(arg: ArrWithGenericStruct) -> ArrWithGenericStruct; + + // these are examples for testing signature and selector generation + fn entry_one(arg: u64) -> u64; + fn sum(a: u64, b: u64) -> u64; + fn sum_test(test: Test) -> u64; + fn takes_array(arg: [str[3]; 3]) -> [str[3]; 2]; + fn take_enum(enum_arg: TestEnum) -> bool; + fn my_struct(my_u64: u64, my_struct: MyStruct) -> u64; + fn array_of_structs(arg1: [MyStructWithEnum; 3]) -> str[3]; + fn complex_function( + arg1: MyGenericStruct<[b256; 3], u8>, + arg2: [MyGenericStruct; 4], + arg3: (str[5], bool), + arg4: MyOtherStruct, + ); +} + +impl MyContract for Contract { + fn test_function() -> bool { + true + } + fn u_8(arg: u8) -> u8 { + arg + } + fn u_16(arg: u16) -> u16 { + arg + } + fn u_32(arg: u32) -> u32 { + arg + } + fn u_64(arg: u64) -> u64 { + arg + } + fn string(arg: str[5]) -> str[5] { + arg + } + fn boolean(arg: bool) -> bool { + arg + } + fn b_256(arg: b256) -> b256 { + arg + } + fn b_512(arg: B512) -> B512 { + arg + } + fn two_args(arg1: b256, arg2: bool) -> (b256, bool) { + (arg1, arg2) + } + fn struct_simple(x: SimpleStruct) -> SimpleStruct { + x + } + fn struct_generic_simple(x: StructB) -> StructB { + x + } + fn struct_with_tuple(x: StructB<(bool, u64)>) -> StructB<(bool, u64)> { + x + } + fn tuple_as_param(x: (u8, StructA, str[3]>)) -> (u8, StructA, str[3]>) { + x + } + fn vector_boolean(x: Vec) -> Vec { + x + } + fn vector_u8(x: Vec) -> Vec { + x + } + fn enum_simple(x: Color) -> Color { + x + } + fn enum_with_builtin_type(x: EnumWithBuiltinType) -> EnumWithBuiltinType { + x + } + fn enum_with_structs(x: EnumWithStructs) -> EnumWithStructs { + x + } + fn array_simple(x: [u8; 4]) -> [u8; 4] { + x + } + fn array_struct(x: [SimpleStruct; 3]) -> [SimpleStruct; 3] { + x + } + fn option_u8(x: Option) -> Option { + x + } + fn arg_then_vector_u8(a: SimpleStruct, x: Vec) -> (SimpleStruct, Vec) { + (a, x) + } + fn vector_u8_then_arg(x: Vec, y: b256) -> (Vec, b256) { + (x, y) + } + fn struct_with_implicit_generics( + arg: StructWithImplicitGenerics, + ) -> StructWithImplicitGenerics { + arg + } + + fn bytes(arg: Bytes) -> Bytes { + arg + } + fn raw_slice(arg: raw_slice) -> raw_slice { + arg + } + + fn dynamic_string(arg: String) -> String { + arg + } + + fn asset_id(arg: AssetId) -> AssetId { + arg + } + + fn two_u8_vectors(x: Vec, y: Vec) -> (Vec, Vec) { + (x, y) + } + fn u32_then_three_vectors_u64( + x: u32, + y: Vec, + z: Vec, + q: Vec, + ) -> (u32, Vec, Vec, Vec) { + (x, y, z, q) + } + + fn return_configurables() -> (u8, bool, [u32; 3], str[4], StructA) { + (U8, BOOL, ARRAY, STR_4, STRUCT) + } + fn vector_inside_vector(arg: Vec>) -> Vec> { + arg + } + fn vector_inside_array(arg: [Vec; 1]) -> [Vec; 1] { + arg + } + fn vector_inside_enum(arg: EnumWithVector) -> EnumWithVector { + arg + } + fn vector_inside_struct(arg: StructWithVector) -> StructWithVector { + arg + } + fn array_with_generic_struct(arg: ArrWithGenericStruct) -> ArrWithGenericStruct { + arg + } + + // these are examples for testing signature and selector generation + fn entry_one(arg: u64) -> u64 { + arg + } + fn sum(a: u64, b: u64) -> u64 { + a + b + } + fn sum_test(test: Test) -> u64 { + test.foo + test.bar + } + fn takes_array(arg: [str[3]; 3]) -> [str[3]; 2] { + [arg[0], arg[1]] + } + fn take_enum(enum_arg: TestEnum) -> bool { + true + } + fn my_struct(my_u64: u64, my_struct: MyStruct) -> u64 { + my_u64 + } + fn array_of_structs(arg1: [MyStructWithEnum; 3]) -> str[3] { + arg1[0].bim + } + fn complex_function( + arg1: MyGenericStruct<[b256; 3], u8>, + arg2: [MyGenericStruct; 4], + arg3: (str[5], bool), + arg4: MyOtherStruct, + ) {} + fn simple_vector(arg: Vec) -> Vec { + arg + } +} diff --git a/packages/fuels/package.json b/packages/fuels/package.json index 2a4a4a4bda1..863dba0d2df 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/cli/commands/deploy/deployPredicates.ts b/packages/fuels/src/cli/commands/deploy/deployPredicates.ts index b3375d85e42..8678b3269cd 100644 --- a/packages/fuels/src/cli/commands/deploy/deployPredicates.ts +++ b/packages/fuels/src/cli/commands/deploy/deployPredicates.ts @@ -30,7 +30,7 @@ export async function deployPredicates(config: FuelsConfig) { const predicate = new Predicate({ abi, bytecode, provider: wallet.provider }); const { bytes: loaderBytecode, - interface: { jsonAbi }, + interface: { specification }, } = await (await predicate.deploy(wallet)).waitForResult(); const predicateRoot = getPredicateRoot(loaderBytecode); @@ -41,7 +41,7 @@ export async function deployPredicates(config: FuelsConfig) { path: predicatePath, predicateRoot, loaderBytecode, - abi: jsonAbi, + abi: specification, }); } diff --git a/packages/fuels/src/cli/commands/deploy/deployScripts.ts b/packages/fuels/src/cli/commands/deploy/deployScripts.ts index 557536d5987..94e1571e57e 100644 --- a/packages/fuels/src/cli/commands/deploy/deployScripts.ts +++ b/packages/fuels/src/cli/commands/deploy/deployScripts.ts @@ -31,7 +31,7 @@ export async function deployScripts(config: FuelsConfig) { const script = new Script(bytecode, abi, wallet); const { bytes: loaderBytecode, - interface: { jsonAbi }, + interface: { specification }, } = await (await script.deploy(wallet)).waitForResult(); debug(`Script deployed: ${projectName}`); @@ -39,7 +39,7 @@ export async function deployScripts(config: FuelsConfig) { scripts.push({ path: scriptPath, loaderBytecode, - abi: jsonAbi, + abi: specification, }); } diff --git a/packages/fuels/src/cli/types.ts b/packages/fuels/src/cli/types.ts index f44c32cd93e..a416d0c8c54 100644 --- a/packages/fuels/src/cli/types.ts +++ b/packages/fuels/src/cli/types.ts @@ -1,4 +1,4 @@ -import type { JsonAbi } from '@fuel-ts/abi-coder'; +import type { AbiSpecification } from '@fuel-ts/abi'; import type { DeployContractOptions } from '@fuel-ts/contract'; export enum Commands { @@ -44,7 +44,7 @@ export type DeployedContract = { export type DeployedScript = { path: string; loaderBytecode: Uint8Array; - abi: JsonAbi; + abi: AbiSpecification; }; export type DeployedPredicate = DeployedScript & { diff --git a/packages/fuels/src/index.test.ts b/packages/fuels/src/index.test.ts index 5528e63af3c..a66158a9657 100644 --- a/packages/fuels/src/index.test.ts +++ b/packages/fuels/src/index.test.ts @@ -8,7 +8,8 @@ describe('index.js', () => { expect(fuels.hexlify).toBeTruthy(); expect(fuels.arrayify).toBeTruthy(); expect(fuels.concat).toBeTruthy(); - expect(fuels.Interface).toBeTruthy(); + + expect(fuels.AbiCoder).toBeTruthy(); expect(fuels.Address).toBeTruthy(); expect(fuels.FuelError).toBeTruthy(); expect(fuels.Contract).toBeTruthy(); @@ -19,6 +20,5 @@ describe('index.js', () => { expect(fuels.TransactionType).toBeTruthy(); expect(fuels.Script).toBeTruthy(); expect(fuels.FunctionInvocationScope).toBeTruthy(); - expect(fuels.Src14OwnedProxy).toBeTruthy(); }); }); diff --git a/packages/fuels/src/index.ts b/packages/fuels/src/index.ts index e13cabc50c4..6ac4592120c 100644 --- a/packages/fuels/src/index.ts +++ b/packages/fuels/src/index.ts @@ -1,6 +1,6 @@ export { Script } from '@fuel-ts/script'; export * from './cli/index'; -export * from '@fuel-ts/abi-coder'; +export * from '@fuel-ts/abi'; export * from '@fuel-ts/address'; export * from '@fuel-ts/address/configs'; export * from '@fuel-ts/contract'; diff --git a/packages/fuels/test/features/deploy.test.ts b/packages/fuels/test/features/deploy.test.ts index c40dec080a1..1f9caa1ea20 100644 --- a/packages/fuels/test/features/deploy.test.ts +++ b/packages/fuels/test/features/deploy.test.ts @@ -1,4 +1,4 @@ -import type { JsonAbi } from '@fuel-ts/abi-coder'; +import type { AbiSpecification } from '@fuel-ts/abi'; import type { Account } from '@fuel-ts/account'; import { Contract } from '@fuel-ts/program'; import { exec } from 'child_process'; @@ -82,7 +82,7 @@ describe('deploy', { timeout: 180000 }, () => { /** * Executes the target contract and returns the values of the functions for proxy deploys. */ - async function executeTargetContract(contractId: string, abi: JsonAbi, wallet: Account) { + async function executeTargetContract(contractId: string, abi: AbiSpecification, wallet: Account) { const targetContract = new Contract(contractId, abi, wallet); const { value: getCountValue } = await targetContract.functions.get_value().get(); diff --git a/packages/interfaces/src/index.ts b/packages/interfaces/src/index.ts index 12e07905429..2702d7106bb 100644 --- a/packages/interfaces/src/index.ts +++ b/packages/interfaces/src/index.ts @@ -78,7 +78,7 @@ export abstract class AbstractAccount { export abstract class AbstractProgram { abstract account: AbstractAccount | null; abstract interface: { - readonly jsonAbi: any; + readonly specification: any; }; abstract provider: { diff --git a/packages/program/package.json b/packages/program/package.json index 8ae76833db3..9fa0489b05e 100644 --- a/packages/program/package.json +++ b/packages/program/package.json @@ -26,7 +26,7 @@ "license": "Apache-2.0", "dependencies": { "ramda": "^0.30.1", - "@fuel-ts/abi-coder": "workspace:*", + "@fuel-ts/abi": "workspace:*", "@fuel-ts/account": "workspace:*", "@fuel-ts/address": "workspace:*", "@fuel-ts/errors": "workspace:*", diff --git a/packages/program/src/contract-call-script.ts b/packages/program/src/contract-call-script.ts index 1d892d65c77..c9e789315fc 100644 --- a/packages/program/src/contract-call-script.ts +++ b/packages/program/src/contract-call-script.ts @@ -1,11 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { - WORD_SIZE, - B256Coder, - ASSET_ID_LEN, - BigNumberCoder, - CONTRACT_ID_LEN, -} from '@fuel-ts/abi-coder'; +import { WORD_SIZE, ASSET_ID_LEN, CONTRACT_ID_LEN, AbiEncoding } from '@fuel-ts/abi'; import type { CallResult, TransactionResultCallReceipt, @@ -25,6 +19,7 @@ import { InstructionSet } from './instruction-set'; import type { EncodedScriptCall, ScriptResult } from './script-request'; import { decodeCallResult, ScriptRequest, calculateScriptDataBaseOffset } from './script-request'; import type { ContractCall, InvocationScopeLike } from './types'; +import {} from '@fuel-ts/abi/dist/coder/encoding/encoding'; type CallOpcodeParamsOffset = { callDataOffset: number; @@ -118,6 +113,7 @@ const scriptResultDecoder = (contractId: AbstractAddress) => (result: ScriptResu ); const mainCallInstructionStart = bn(mainCallResult?.is); + const coders = AbiEncoding.v1.coders; const receipts = result.receipts as ReturnReceipt[]; return receipts .filter(({ type }) => isReturnType(type)) @@ -126,7 +122,7 @@ const scriptResultDecoder = (contractId: AbstractAddress) => (result: ScriptResu return []; } if (receipt.type === ReceiptType.Return) { - return [new BigNumberCoder('u64').encode((receipt as TransactionResultReturnReceipt).val)]; + return [coders.u64.encode((receipt as TransactionResultReturnReceipt).val)]; } if (receipt.type === ReceiptType.ReturnData) { const encodedScriptReturn = arrayify(receipt.data); @@ -187,6 +183,7 @@ export const getContractCallScript = ( // the data about the contract output let segmentOffset = dataOffset; + const coders = AbiEncoding.v1.coders; const scriptData: Uint8Array[] = []; for (let i = 0; i < TOTAL_CALLS; i += 1) { const call = contractCalls[i]; @@ -200,15 +197,15 @@ export const getContractCallScript = ( let gasForwardedOffset = 0; // 1. Amount - scriptData.push(new BigNumberCoder('u64').encode(call.amount || 0)); + scriptData.push(coders.u64.encode(call.amount || 0)); // 2. Asset ID - scriptData.push(new B256Coder().encode(call.assetId?.toString() || ZeroBytes32)); + scriptData.push(coders.b256.encode(call.assetId?.toString() || ZeroBytes32)); // 3. Contract ID scriptData.push(call.contractId.toBytes()); // 4. Function selector offset - scriptData.push(new BigNumberCoder('u64').encode(encodedSelectorOffset)); + scriptData.push(coders.u64.encode(encodedSelectorOffset)); // 5. Encoded argument offset - scriptData.push(new BigNumberCoder('u64').encode(encodedArgsOffset)); + scriptData.push(coders.u64.encode(encodedArgsOffset)); // 6. Encoded function selector scriptData.push(call.fnSelectorBytes); // 7. Encoded arguments @@ -216,7 +213,7 @@ export const getContractCallScript = ( // 8. Gas to be forwarded if (call.gas) { - scriptData.push(new BigNumberCoder('u64').encode(call.gas)); + scriptData.push(coders.u64.encode(call.gas)); gasForwardedOffset = encodedArgsOffset + encodedArgs.byteLength; } diff --git a/packages/program/src/contract.test.ts b/packages/program/src/contract.test.ts index a99ab1dc60a..8852ff272ed 100644 --- a/packages/program/src/contract.test.ts +++ b/packages/program/src/contract.test.ts @@ -1,11 +1,11 @@ -import type { JsonAbi } from '@fuel-ts/abi-coder'; +import type { AbiSpecification } from '@fuel-ts/abi'; import { Account } from '@fuel-ts/account'; import { setupTestProviderAndWallets } from '@fuel-ts/account/test-utils'; import Contract from './contract'; const CONTRACT_ID = '0x0101010101010101010101010101010101010101010101010101010101010101'; -const ABI: JsonAbi = { +const ABI: AbiSpecification = { concreteTypes: [ { concreteTypeId: 'asdf', diff --git a/packages/program/src/contract.ts b/packages/program/src/contract.ts index e1caf4e8c21..34c902ff1aa 100644 --- a/packages/program/src/contract.ts +++ b/packages/program/src/contract.ts @@ -1,5 +1,5 @@ -import type { FunctionFragment, JsonAbi } from '@fuel-ts/abi-coder'; -import { Interface } from '@fuel-ts/abi-coder'; +import type { AbiSpecification, AbiCoderFunction } from '@fuel-ts/abi'; +import { AbiCoder } from '@fuel-ts/abi'; import type { Account, Provider } from '@fuel-ts/account'; import { Address } from '@fuel-ts/address'; import type { AbstractAddress, AbstractContract, BytesLike } from '@fuel-ts/interfaces'; @@ -25,7 +25,7 @@ export default class Contract implements AbstractContract { /** * The contract's ABI interface. */ - interface!: Interface; + interface!: AbiCoder; /** * The account associated with the contract, if available. @@ -46,10 +46,10 @@ export default class Contract implements AbstractContract { */ constructor( id: string | AbstractAddress, - abi: JsonAbi | Interface, + abi: AbiSpecification | AbiCoder, accountOrProvider: Account | Provider ) { - this.interface = abi instanceof Interface ? abi : new Interface(abi); + this.interface = abi instanceof AbiCoder ? abi : AbiCoder.fromAbi(abi); this.id = Address.fromAddressOrString(id); /** @@ -88,7 +88,7 @@ export default class Contract implements AbstractContract { * @param func - The function fragment to build a scope for. * @returns A function that creates a FunctionInvocationScope. */ - buildFunction(func: FunctionFragment) { + buildFunction(func: AbiCoderFunction) { return (() => { const funcInvocationScopeCreator = (...args: Array) => new FunctionInvocationScope(this, func, args); diff --git a/packages/program/src/functions/base-invocation-scope.ts b/packages/program/src/functions/base-invocation-scope.ts index e578954d9a0..a4eaec8f456 100644 --- a/packages/program/src/functions/base-invocation-scope.ts +++ b/packages/program/src/functions/base-invocation-scope.ts @@ -1,6 +1,6 @@ /* eslint-disable no-param-reassign */ /* eslint-disable @typescript-eslint/no-explicit-any */ -import type { InputValue, JsonAbi } from '@fuel-ts/abi-coder'; +import type { AbiSpecification, InputValue } from '@fuel-ts/abi'; import type { Provider, CoinQuantity, @@ -64,7 +64,7 @@ export class BaseInvocationScope { protected requiredCoins: CoinQuantity[] = []; protected isMultiCall: boolean = false; protected hasCallParamsGasLimit: boolean = false; // flag to check if any of the callParams has gasLimit set - protected externalAbis: Record = {}; + protected externalAbis: Record = {}; private addSignersCallback?: ( txRequest: ScriptTransactionRequest ) => Promise; @@ -305,7 +305,7 @@ export class BaseInvocationScope { addContracts(contracts: Array) { contracts.forEach((contract) => { this.transactionRequest.addContractInputAndOutput(contract.id); - this.externalAbis[contract.id.toB256()] = contract.interface.jsonAbi; + this.externalAbis[contract.id.toB256()] = contract.interface.specification; }); return this; } diff --git a/packages/program/src/functions/invocation-scope.ts b/packages/program/src/functions/invocation-scope.ts index 70c69106fde..7e53055b5be 100644 --- a/packages/program/src/functions/invocation-scope.ts +++ b/packages/program/src/functions/invocation-scope.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import type { FunctionFragment } from '@fuel-ts/abi-coder'; +import type { AbiCoderFunction } from '@fuel-ts/abi'; import type { CoinQuantity } from '@fuel-ts/account'; import { coinQuantityfy } from '@fuel-ts/account'; import { ErrorCode, FuelError } from '@fuel-ts/errors'; @@ -19,7 +19,7 @@ export class FunctionInvocationScope< TArgs extends Array = Array, TReturn = any, > extends BaseInvocationScope { - protected func: FunctionFragment; + protected func: AbiCoderFunction; private callParameters?: CallParams; private forward?: CoinQuantity; protected args: TArgs; @@ -31,7 +31,7 @@ export class FunctionInvocationScope< * @param func - The function fragment. * @param args - The arguments. */ - constructor(program: AbstractProgram, func: FunctionFragment, args: TArgs) { + constructor(program: AbstractProgram, func: AbiCoderFunction, args: TArgs) { super(program, false); this.func = func; this.args = args || []; @@ -81,7 +81,7 @@ export class FunctionInvocationScope< this.callParameters = callParams; if (callParams?.forward) { - if (!this.func.attributes.find((attr) => attr.name === 'payable')) { + if (!this.func.attributes?.find((attr) => attr.name === 'payable')) { throw new FuelError( ErrorCode.TRANSACTION_ERROR, `The target function ${this.func.name} cannot accept forwarded funds as it's not marked as 'payable'.` diff --git a/packages/program/src/response.ts b/packages/program/src/response.ts index 2389ae94180..a52a91eb96c 100644 --- a/packages/program/src/response.ts +++ b/packages/program/src/response.ts @@ -34,7 +34,7 @@ export const extractInvocationResult = ( const decodedResults = encodedResults.map((encodedResult, i) => { const { func } = functionScopes[i].getCallConfig(); - return func.decodeOutput(encodedResult)?.[0]; + return func.decodeOutput(encodedResult); }); return (isMultiCall ? decodedResults : decodedResults?.[0]) as T; diff --git a/packages/program/src/script-request.ts b/packages/program/src/script-request.ts index a5f456e7963..a18cacc267f 100644 --- a/packages/program/src/script-request.ts +++ b/packages/program/src/script-request.ts @@ -5,7 +5,7 @@ import { SCRIPT_FIXED_SIZE, WORD_SIZE, calculateVmTxMemory, -} from '@fuel-ts/abi-coder'; +} from '@fuel-ts/abi'; import type { TransactionResultReturnDataReceipt, TransactionResultRevertReceipt, @@ -161,8 +161,7 @@ export function callResultToInvocationResult( value = scriptResult.returnReceipt.val; } if (scriptResult.returnReceipt.type === ReceiptType.ReturnData) { - const decoded = call.func.decodeOutput(scriptResult.returnReceipt.data); - value = decoded[0]; + value = call.func.decodeOutput(scriptResult.returnReceipt.data); } return value as TReturn; diff --git a/packages/program/src/types.ts b/packages/program/src/types.ts index 258f98c277f..64739086da5 100644 --- a/packages/program/src/types.ts +++ b/packages/program/src/types.ts @@ -1,4 +1,4 @@ -import type { FunctionFragment, JsonAbi } from '@fuel-ts/abi-coder'; +import type { AbiCoderFunction, AbiSpecification } from '@fuel-ts/abi'; import type { CallResult, CoinQuantity, @@ -22,7 +22,7 @@ export type ContractCall = { amount?: BigNumberish; assetId?: BytesLike; gas?: BigNumberish; - externalContractsAbis?: Record; + externalContractsAbis?: Record; }; /** @@ -51,12 +51,12 @@ export type TxParams = Partial<{ * @template T - Type of the function's arguments. */ export type CallConfig = { - func: FunctionFragment; + func: AbiCoderFunction; program: AbstractProgram; callParameters?: CallParams; txParameters?: TxParams; forward?: CoinQuantity; - externalAbis: Record; + externalAbis: Record; args: T; }; diff --git a/packages/program/src/utils.ts b/packages/program/src/utils.ts index def6899b9b8..f3ba84874d3 100644 --- a/packages/program/src/utils.ts +++ b/packages/program/src/utils.ts @@ -28,10 +28,11 @@ export function getAbisFromAllCalls( const { program, externalAbis } = funcScope.getCallConfig(); if (i === 0) { - acc.main = program.interface.jsonAbi; + acc.main = program.interface.specification; acc.otherContractsAbis = {}; } else { - acc.otherContractsAbis[(program).id.toB256()] = program.interface.jsonAbi; + acc.otherContractsAbis[(program).id.toB256()] = + program.interface.specification; } acc.otherContractsAbis = { ...acc.otherContractsAbis, ...externalAbis }; diff --git a/packages/recipes/package.json b/packages/recipes/package.json index 5f9f46e216b..98e7af40a7a 100644 --- a/packages/recipes/package.json +++ b/packages/recipes/package.json @@ -26,13 +26,13 @@ }, "license": "Apache-2.0", "dependencies": { - "@fuel-ts/abi-coder": "workspace:*", + "@fuel-ts/abi": "workspace:*", "@fuel-ts/abi-typegen": "workspace:*", "@fuel-ts/account": "workspace:*", + "@fuel-ts/contract": "workspace:*", "@fuel-ts/interfaces": "workspace:*", "@fuel-ts/program": "workspace:*", "@fuel-ts/transactions": "workspace:*", - "@fuel-ts/utils": "workspace:*", - "@fuel-ts/contract": "workspace:*" + "@fuel-ts/utils": "workspace:*" } } diff --git a/packages/recipes/scripts/build-recipes.ts b/packages/recipes/scripts/build-recipes.ts index 0e4003f3f86..b4187e36923 100644 --- a/packages/recipes/scripts/build-recipes.ts +++ b/packages/recipes/scripts/build-recipes.ts @@ -6,15 +6,16 @@ execSync(`fuels-typegen -i src/contracts/**/*-abi.json -o src/types`); const supportedRecipes = ['Src14OwnedProxy'].map((s) => [s, `${s}Factory`]).flat(); const importReplacementMap = { + AbiCoder: '@fuel-ts/abi', + AbiCoderFunction: '@fuel-ts/abi', + AbiSpecification: '@fuel-ts/abi', Contract: '@fuel-ts/program', ContractFactory: '@fuel-ts/contract', DeployContractOptions: '@fuel-ts/contract', - Interface: '@fuel-ts/abi-coder', Provider: '@fuel-ts/account', Account: '@fuel-ts/account', StorageSlot: '@fuel-ts/transactions', AbstractAddress: '@fuel-ts/interfaces', - FunctionFragment: '@fuel-ts/abi-coder', InvokeFunction: '@fuel-ts/program', StrSlice: '@fuel-ts/interfaces', decompressBytecode: '@fuel-ts/utils', diff --git a/packages/recipes/src/types/Src14OwnedProxy.ts b/packages/recipes/src/types/Src14OwnedProxy.ts index d5b737e7c35..9e6b90763ad 100644 --- a/packages/recipes/src/types/Src14OwnedProxy.ts +++ b/packages/recipes/src/types/Src14OwnedProxy.ts @@ -8,39 +8,26 @@ Fuels version: 0.97.0 */ + + + + import { Contract, type InvokeFunction } from '@fuel-ts/program'; -import { Interface, type FunctionFragment } from '@fuel-ts/abi-coder'; +import { AbiCoder, type AbiSpecification, type AbiCoderFunction } from '@fuel-ts/abi'; import { type Provider, type Account } from '@fuel-ts/account'; import { type StorageSlot } from '@fuel-ts/transactions'; -import { type AbstractAddress, type StrSlice } from '@fuel-ts/interfaces'; -import type { Option, Enum } from './common'; +import { type AbstractAddress, type StrSlice } from '@fuel-ts/interfaces';import type { Option, Enum } from "./common"; -export enum AccessErrorInput { - NotOwner = 'NotOwner', -} -export enum AccessErrorOutput { - NotOwner = 'NotOwner', -} -export type IdentityInput = Enum<{ Address: AddressInput; ContractId: ContractIdInput }>; -export type IdentityOutput = Enum<{ Address: AddressOutput; ContractId: ContractIdOutput }>; -export enum InitializationErrorInput { - CannotReinitialized = 'CannotReinitialized', -} -export enum InitializationErrorOutput { - CannotReinitialized = 'CannotReinitialized', -} -export enum SetProxyOwnerErrorInput { - CannotUninitialize = 'CannotUninitialize', -} -export enum SetProxyOwnerErrorOutput { - CannotUninitialize = 'CannotUninitialize', -} -export type StateInput = Enum<{ - Uninitialized: undefined; - Initialized: IdentityInput; - Revoked: undefined; -}>; -export type StateOutput = Enum<{ Uninitialized: void; Initialized: IdentityOutput; Revoked: void }>; +export enum AccessErrorInput { NotOwner = 'NotOwner' }; +export enum AccessErrorOutput { NotOwner = 'NotOwner' }; +export type IdentityInput = Enum<{ Address: AddressInput, ContractId: ContractIdInput }>; +export type IdentityOutput = Enum<{ Address: AddressOutput, ContractId: ContractIdOutput }>; +export enum InitializationErrorInput { CannotReinitialized = 'CannotReinitialized' }; +export enum InitializationErrorOutput { CannotReinitialized = 'CannotReinitialized' }; +export enum SetProxyOwnerErrorInput { CannotUninitialize = 'CannotUninitialize' }; +export enum SetProxyOwnerErrorOutput { CannotUninitialize = 'CannotUninitialize' }; +export type StateInput = Enum<{ Uninitialized: undefined, Initialized: IdentityInput, Revoked: undefined }>; +export type StateOutput = Enum<{ Uninitialized: void, Initialized: IdentityOutput, Revoked: void }>; export type AddressInput = { bits: string }; export type AddressOutput = AddressInput; @@ -56,614 +43,758 @@ export type Src14OwnedProxyConfigurables = Partial<{ INITIAL_OWNER: StateInput; }>; -const abi = { - programType: 'contract', - specVersion: '1', - encodingVersion: '1', - concreteTypes: [ - { - type: '()', - concreteTypeId: '2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d', - }, +const abi: AbiSpecification = { + "programType": "contract", + "specVersion": "1", + "encodingVersion": "1", + "concreteTypes": [ { - type: 'enum standards::src5::AccessError', - concreteTypeId: '3f702ea3351c9c1ece2b84048006c8034a24cbc2bad2e740d0412b4172951d3d', - metadataTypeId: 1, + "type": "()", + "concreteTypeId": "2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d" }, { - type: 'enum standards::src5::State', - concreteTypeId: '192bc7098e2fe60635a9918afb563e4e5419d386da2bdbf0d716b4bc8549802c', - metadataTypeId: 2, + "type": "enum standards::src5::AccessError", + "concreteTypeId": "3f702ea3351c9c1ece2b84048006c8034a24cbc2bad2e740d0412b4172951d3d", + "metadataTypeId": 1 }, { - type: 'enum std::option::Option', - concreteTypeId: '0d79387ad3bacdc3b7aad9da3a96f4ce60d9a1b6002df254069ad95a3931d5c8', - metadataTypeId: 4, - typeArguments: ['29c10735d33b5159f0c71ee1dbd17b36a3e69e41f00fab0d42e1bd9f428d8a54'], + "type": "enum standards::src5::State", + "concreteTypeId": "192bc7098e2fe60635a9918afb563e4e5419d386da2bdbf0d716b4bc8549802c", + "metadataTypeId": 2 }, { - type: 'enum sway_libs::ownership::errors::InitializationError', - concreteTypeId: '1dfe7feadc1d9667a4351761230f948744068a090fe91b1bc6763a90ed5d3893', - metadataTypeId: 5, + "type": "enum std::option::Option", + "concreteTypeId": "0d79387ad3bacdc3b7aad9da3a96f4ce60d9a1b6002df254069ad95a3931d5c8", + "metadataTypeId": 4, + "typeArguments": [ + "29c10735d33b5159f0c71ee1dbd17b36a3e69e41f00fab0d42e1bd9f428d8a54" + ] }, { - type: 'enum sway_libs::upgradability::errors::SetProxyOwnerError', - concreteTypeId: '3c6e90ae504df6aad8b34a93ba77dc62623e00b777eecacfa034a8ac6e890c74', - metadataTypeId: 6, + "type": "enum sway_libs::ownership::errors::InitializationError", + "concreteTypeId": "1dfe7feadc1d9667a4351761230f948744068a090fe91b1bc6763a90ed5d3893", + "metadataTypeId": 5 }, { - type: 'str', - concreteTypeId: '8c25cb3686462e9a86d2883c5688a22fe738b0bbc85f458d2d2b5f3f667c6d5a', + "type": "enum sway_libs::upgradability::errors::SetProxyOwnerError", + "concreteTypeId": "3c6e90ae504df6aad8b34a93ba77dc62623e00b777eecacfa034a8ac6e890c74", + "metadataTypeId": 6 }, { - type: 'struct std::contract_id::ContractId', - concreteTypeId: '29c10735d33b5159f0c71ee1dbd17b36a3e69e41f00fab0d42e1bd9f428d8a54', - metadataTypeId: 9, + "type": "str", + "concreteTypeId": "8c25cb3686462e9a86d2883c5688a22fe738b0bbc85f458d2d2b5f3f667c6d5a" }, { - type: 'struct sway_libs::upgradability::events::ProxyOwnerSet', - concreteTypeId: '96dd838b44f99d8ccae2a7948137ab6256c48ca4abc6168abc880de07fba7247', - metadataTypeId: 10, + "type": "struct std::contract_id::ContractId", + "concreteTypeId": "29c10735d33b5159f0c71ee1dbd17b36a3e69e41f00fab0d42e1bd9f428d8a54", + "metadataTypeId": 9 }, { - type: 'struct sway_libs::upgradability::events::ProxyTargetSet', - concreteTypeId: '1ddc0adda1270a016c08ffd614f29f599b4725407c8954c8b960bdf651a9a6c8', - metadataTypeId: 11, + "type": "struct sway_libs::upgradability::events::ProxyOwnerSet", + "concreteTypeId": "96dd838b44f99d8ccae2a7948137ab6256c48ca4abc6168abc880de07fba7247", + "metadataTypeId": 10 }, + { + "type": "struct sway_libs::upgradability::events::ProxyTargetSet", + "concreteTypeId": "1ddc0adda1270a016c08ffd614f29f599b4725407c8954c8b960bdf651a9a6c8", + "metadataTypeId": 11 + } ], - metadataTypes: [ + "metadataTypes": [ { - type: 'b256', - metadataTypeId: 0, + "type": "b256", + "metadataTypeId": 0 }, { - type: 'enum standards::src5::AccessError', - metadataTypeId: 1, - components: [ + "type": "enum standards::src5::AccessError", + "metadataTypeId": 1, + "components": [ { - name: 'NotOwner', - typeId: '2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d', - }, - ], + "name": "NotOwner", + "typeId": "2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d" + } + ] }, { - type: 'enum standards::src5::State', - metadataTypeId: 2, - components: [ + "type": "enum standards::src5::State", + "metadataTypeId": 2, + "components": [ { - name: 'Uninitialized', - typeId: '2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d', + "name": "Uninitialized", + "typeId": "2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d" }, { - name: 'Initialized', - typeId: 3, + "name": "Initialized", + "typeId": 3 }, { - name: 'Revoked', - typeId: '2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d', - }, - ], + "name": "Revoked", + "typeId": "2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d" + } + ] }, { - type: 'enum std::identity::Identity', - metadataTypeId: 3, - components: [ + "type": "enum std::identity::Identity", + "metadataTypeId": 3, + "components": [ { - name: 'Address', - typeId: 8, + "name": "Address", + "typeId": 8 }, { - name: 'ContractId', - typeId: 9, - }, - ], + "name": "ContractId", + "typeId": 9 + } + ] }, { - type: 'enum std::option::Option', - metadataTypeId: 4, - components: [ + "type": "enum std::option::Option", + "metadataTypeId": 4, + "components": [ { - name: 'None', - typeId: '2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d', + "name": "None", + "typeId": "2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d" }, { - name: 'Some', - typeId: 7, - }, + "name": "Some", + "typeId": 7 + } ], - typeParameters: [7], + "typeParameters": [ + 7 + ] }, { - type: 'enum sway_libs::ownership::errors::InitializationError', - metadataTypeId: 5, - components: [ + "type": "enum sway_libs::ownership::errors::InitializationError", + "metadataTypeId": 5, + "components": [ { - name: 'CannotReinitialized', - typeId: '2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d', - }, - ], + "name": "CannotReinitialized", + "typeId": "2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d" + } + ] }, { - type: 'enum sway_libs::upgradability::errors::SetProxyOwnerError', - metadataTypeId: 6, - components: [ + "type": "enum sway_libs::upgradability::errors::SetProxyOwnerError", + "metadataTypeId": 6, + "components": [ { - name: 'CannotUninitialize', - typeId: '2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d', - }, - ], + "name": "CannotUninitialize", + "typeId": "2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d" + } + ] }, { - type: 'generic T', - metadataTypeId: 7, + "type": "generic T", + "metadataTypeId": 7 }, { - type: 'struct std::address::Address', - metadataTypeId: 8, - components: [ + "type": "struct std::address::Address", + "metadataTypeId": 8, + "components": [ { - name: 'bits', - typeId: 0, - }, - ], + "name": "bits", + "typeId": 0 + } + ] }, { - type: 'struct std::contract_id::ContractId', - metadataTypeId: 9, - components: [ + "type": "struct std::contract_id::ContractId", + "metadataTypeId": 9, + "components": [ { - name: 'bits', - typeId: 0, - }, - ], + "name": "bits", + "typeId": 0 + } + ] }, { - type: 'struct sway_libs::upgradability::events::ProxyOwnerSet', - metadataTypeId: 10, - components: [ + "type": "struct sway_libs::upgradability::events::ProxyOwnerSet", + "metadataTypeId": 10, + "components": [ { - name: 'new_proxy_owner', - typeId: 2, - }, - ], + "name": "new_proxy_owner", + "typeId": 2 + } + ] }, { - type: 'struct sway_libs::upgradability::events::ProxyTargetSet', - metadataTypeId: 11, - components: [ + "type": "struct sway_libs::upgradability::events::ProxyTargetSet", + "metadataTypeId": 11, + "components": [ { - name: 'new_target', - typeId: 9, - }, - ], - }, + "name": "new_target", + "typeId": 9 + } + ] + } ], - functions: [ + "functions": [ { - inputs: [], - name: 'proxy_target', - output: '0d79387ad3bacdc3b7aad9da3a96f4ce60d9a1b6002df254069ad95a3931d5c8', - attributes: [ + "inputs": [], + "name": "proxy_target", + "output": "0d79387ad3bacdc3b7aad9da3a96f4ce60d9a1b6002df254069ad95a3931d5c8", + "attributes": [ { - name: 'doc-comment', - arguments: [' Returns the target contract of the proxy contract.'], + "name": "doc-comment", + "arguments": [ + " Returns the target contract of the proxy contract." + ] }, { - name: 'doc-comment', - arguments: [''], + "name": "doc-comment", + "arguments": [ + "" + ] }, { - name: 'doc-comment', - arguments: [' # Returns'], + "name": "doc-comment", + "arguments": [ + " # Returns" + ] }, { - name: 'doc-comment', - arguments: [''], + "name": "doc-comment", + "arguments": [ + "" + ] }, { - name: 'doc-comment', - arguments: [ - ' * [Option] - The new proxy contract to which all fallback calls will be passed or `None`.', - ], + "name": "doc-comment", + "arguments": [ + " * [Option] - The new proxy contract to which all fallback calls will be passed or `None`." + ] }, { - name: 'doc-comment', - arguments: [''], + "name": "doc-comment", + "arguments": [ + "" + ] }, { - name: 'doc-comment', - arguments: [' # Number of Storage Accesses'], + "name": "doc-comment", + "arguments": [ + " # Number of Storage Accesses" + ] }, { - name: 'doc-comment', - arguments: [''], + "name": "doc-comment", + "arguments": [ + "" + ] }, { - name: 'doc-comment', - arguments: [' * Reads: `1`'], + "name": "doc-comment", + "arguments": [ + " * Reads: `1`" + ] }, { - name: 'storage', - arguments: ['read'], - }, - ], + "name": "storage", + "arguments": [ + "read" + ] + } + ] }, { - inputs: [ + "inputs": [ { - name: 'new_target', - concreteTypeId: '29c10735d33b5159f0c71ee1dbd17b36a3e69e41f00fab0d42e1bd9f428d8a54', - }, + "name": "new_target", + "concreteTypeId": "29c10735d33b5159f0c71ee1dbd17b36a3e69e41f00fab0d42e1bd9f428d8a54" + } ], - name: 'set_proxy_target', - output: '2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d', - attributes: [ + "name": "set_proxy_target", + "output": "2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d", + "attributes": [ { - name: 'doc-comment', - arguments: [' Change the target contract of the proxy contract.'], + "name": "doc-comment", + "arguments": [ + " Change the target contract of the proxy contract." + ] }, { - name: 'doc-comment', - arguments: [''], + "name": "doc-comment", + "arguments": [ + "" + ] }, { - name: 'doc-comment', - arguments: [' # Additional Information'], + "name": "doc-comment", + "arguments": [ + " # Additional Information" + ] }, { - name: 'doc-comment', - arguments: [''], + "name": "doc-comment", + "arguments": [ + "" + ] }, { - name: 'doc-comment', - arguments: [' This method can only be called by the `proxy_owner`.'], + "name": "doc-comment", + "arguments": [ + " This method can only be called by the `proxy_owner`." + ] }, { - name: 'doc-comment', - arguments: [''], + "name": "doc-comment", + "arguments": [ + "" + ] }, { - name: 'doc-comment', - arguments: [' # Arguments'], + "name": "doc-comment", + "arguments": [ + " # Arguments" + ] }, { - name: 'doc-comment', - arguments: [''], + "name": "doc-comment", + "arguments": [ + "" + ] }, { - name: 'doc-comment', - arguments: [ - ' * `new_target`: [ContractId] - The new proxy contract to which all fallback calls will be passed.', - ], + "name": "doc-comment", + "arguments": [ + " * `new_target`: [ContractId] - The new proxy contract to which all fallback calls will be passed." + ] }, { - name: 'doc-comment', - arguments: [''], + "name": "doc-comment", + "arguments": [ + "" + ] }, { - name: 'doc-comment', - arguments: [' # Reverts'], + "name": "doc-comment", + "arguments": [ + " # Reverts" + ] }, { - name: 'doc-comment', - arguments: [''], + "name": "doc-comment", + "arguments": [ + "" + ] }, { - name: 'doc-comment', - arguments: [' * When not called by `proxy_owner`.'], + "name": "doc-comment", + "arguments": [ + " * When not called by `proxy_owner`." + ] }, { - name: 'doc-comment', - arguments: [''], + "name": "doc-comment", + "arguments": [ + "" + ] }, { - name: 'doc-comment', - arguments: [' # Number of Storage Accesses'], + "name": "doc-comment", + "arguments": [ + " # Number of Storage Accesses" + ] }, { - name: 'doc-comment', - arguments: [''], + "name": "doc-comment", + "arguments": [ + "" + ] }, { - name: 'doc-comment', - arguments: [' * Reads: `1`'], + "name": "doc-comment", + "arguments": [ + " * Reads: `1`" + ] }, { - name: 'doc-comment', - arguments: [' * Write: `1`'], + "name": "doc-comment", + "arguments": [ + " * Write: `1`" + ] }, { - name: 'storage', - arguments: ['read', 'write'], - }, - ], + "name": "storage", + "arguments": [ + "read", + "write" + ] + } + ] }, { - inputs: [], - name: 'proxy_owner', - output: '192bc7098e2fe60635a9918afb563e4e5419d386da2bdbf0d716b4bc8549802c', - attributes: [ + "inputs": [], + "name": "proxy_owner", + "output": "192bc7098e2fe60635a9918afb563e4e5419d386da2bdbf0d716b4bc8549802c", + "attributes": [ { - name: 'doc-comment', - arguments: [' Returns the owner of the proxy contract.'], + "name": "doc-comment", + "arguments": [ + " Returns the owner of the proxy contract." + ] }, { - name: 'doc-comment', - arguments: [''], + "name": "doc-comment", + "arguments": [ + "" + ] }, { - name: 'doc-comment', - arguments: [' # Returns'], + "name": "doc-comment", + "arguments": [ + " # Returns" + ] }, { - name: 'doc-comment', - arguments: [''], + "name": "doc-comment", + "arguments": [ + "" + ] }, { - name: 'doc-comment', - arguments: [' * [State] - Represents the state of ownership for this contract.'], + "name": "doc-comment", + "arguments": [ + " * [State] - Represents the state of ownership for this contract." + ] }, { - name: 'doc-comment', - arguments: [''], + "name": "doc-comment", + "arguments": [ + "" + ] }, { - name: 'doc-comment', - arguments: [' # Number of Storage Accesses'], + "name": "doc-comment", + "arguments": [ + " # Number of Storage Accesses" + ] }, { - name: 'doc-comment', - arguments: [''], + "name": "doc-comment", + "arguments": [ + "" + ] }, { - name: 'doc-comment', - arguments: [' * Reads: `1`'], + "name": "doc-comment", + "arguments": [ + " * Reads: `1`" + ] }, { - name: 'storage', - arguments: ['read'], - }, - ], + "name": "storage", + "arguments": [ + "read" + ] + } + ] }, { - inputs: [], - name: 'initialize_proxy', - output: '2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d', - attributes: [ + "inputs": [], + "name": "initialize_proxy", + "output": "2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d", + "attributes": [ { - name: 'doc-comment', - arguments: [' Initializes the proxy contract.'], + "name": "doc-comment", + "arguments": [ + " Initializes the proxy contract." + ] }, { - name: 'doc-comment', - arguments: [''], + "name": "doc-comment", + "arguments": [ + "" + ] }, { - name: 'doc-comment', - arguments: [' # Additional Information'], + "name": "doc-comment", + "arguments": [ + " # Additional Information" + ] }, { - name: 'doc-comment', - arguments: [''], + "name": "doc-comment", + "arguments": [ + "" + ] }, { - name: 'doc-comment', - arguments: [ - ' This method sets the storage values using the values of the configurable constants `INITIAL_TARGET` and `INITIAL_OWNER`.', - ], + "name": "doc-comment", + "arguments": [ + " This method sets the storage values using the values of the configurable constants `INITIAL_TARGET` and `INITIAL_OWNER`." + ] }, { - name: 'doc-comment', - arguments: [' This then allows methods that write to storage to be called.'], + "name": "doc-comment", + "arguments": [ + " This then allows methods that write to storage to be called." + ] }, { - name: 'doc-comment', - arguments: [' This method can only be called once.'], + "name": "doc-comment", + "arguments": [ + " This method can only be called once." + ] }, { - name: 'doc-comment', - arguments: [''], + "name": "doc-comment", + "arguments": [ + "" + ] }, { - name: 'doc-comment', - arguments: [' # Reverts'], + "name": "doc-comment", + "arguments": [ + " # Reverts" + ] }, { - name: 'doc-comment', - arguments: [''], + "name": "doc-comment", + "arguments": [ + "" + ] }, { - name: 'doc-comment', - arguments: [' * When `storage::SRC14.proxy_owner` is not [State::Uninitialized].'], + "name": "doc-comment", + "arguments": [ + " * When `storage::SRC14.proxy_owner` is not [State::Uninitialized]." + ] }, { - name: 'doc-comment', - arguments: [''], + "name": "doc-comment", + "arguments": [ + "" + ] }, { - name: 'doc-comment', - arguments: [' # Number of Storage Accesses'], + "name": "doc-comment", + "arguments": [ + " # Number of Storage Accesses" + ] }, { - name: 'doc-comment', - arguments: [''], + "name": "doc-comment", + "arguments": [ + "" + ] }, { - name: 'doc-comment', - arguments: [' * Writes: `2`'], + "name": "doc-comment", + "arguments": [ + " * Writes: `2`" + ] }, { - name: 'storage', - arguments: ['write'], - }, - ], + "name": "storage", + "arguments": [ + "write" + ] + } + ] }, { - inputs: [ + "inputs": [ { - name: 'new_proxy_owner', - concreteTypeId: '192bc7098e2fe60635a9918afb563e4e5419d386da2bdbf0d716b4bc8549802c', - }, + "name": "new_proxy_owner", + "concreteTypeId": "192bc7098e2fe60635a9918afb563e4e5419d386da2bdbf0d716b4bc8549802c" + } ], - name: 'set_proxy_owner', - output: '2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d', - attributes: [ + "name": "set_proxy_owner", + "output": "2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d", + "attributes": [ { - name: 'doc-comment', - arguments: [' Changes proxy ownership to the passed State.'], + "name": "doc-comment", + "arguments": [ + " Changes proxy ownership to the passed State." + ] }, { - name: 'doc-comment', - arguments: [''], + "name": "doc-comment", + "arguments": [ + "" + ] }, { - name: 'doc-comment', - arguments: [' # Additional Information'], + "name": "doc-comment", + "arguments": [ + " # Additional Information" + ] }, { - name: 'doc-comment', - arguments: [''], + "name": "doc-comment", + "arguments": [ + "" + ] }, { - name: 'doc-comment', - arguments: [ - ' This method can be used to transfer ownership between Identities or to revoke ownership.', - ], + "name": "doc-comment", + "arguments": [ + " This method can be used to transfer ownership between Identities or to revoke ownership." + ] }, { - name: 'doc-comment', - arguments: [''], + "name": "doc-comment", + "arguments": [ + "" + ] }, { - name: 'doc-comment', - arguments: [' # Arguments'], + "name": "doc-comment", + "arguments": [ + " # Arguments" + ] }, { - name: 'doc-comment', - arguments: [''], + "name": "doc-comment", + "arguments": [ + "" + ] }, { - name: 'doc-comment', - arguments: [' * `new_proxy_owner`: [State] - The new state of the proxy ownership.'], + "name": "doc-comment", + "arguments": [ + " * `new_proxy_owner`: [State] - The new state of the proxy ownership." + ] }, { - name: 'doc-comment', - arguments: [''], + "name": "doc-comment", + "arguments": [ + "" + ] }, { - name: 'doc-comment', - arguments: [' # Reverts'], + "name": "doc-comment", + "arguments": [ + " # Reverts" + ] }, { - name: 'doc-comment', - arguments: [''], + "name": "doc-comment", + "arguments": [ + "" + ] }, { - name: 'doc-comment', - arguments: [' * When the sender is not the current proxy owner.'], + "name": "doc-comment", + "arguments": [ + " * When the sender is not the current proxy owner." + ] }, { - name: 'doc-comment', - arguments: [' * When the new state of the proxy ownership is [State::Uninitialized].'], + "name": "doc-comment", + "arguments": [ + " * When the new state of the proxy ownership is [State::Uninitialized]." + ] }, { - name: 'doc-comment', - arguments: [''], + "name": "doc-comment", + "arguments": [ + "" + ] }, { - name: 'doc-comment', - arguments: [' # Number of Storage Accesses'], + "name": "doc-comment", + "arguments": [ + " # Number of Storage Accesses" + ] }, { - name: 'doc-comment', - arguments: [''], + "name": "doc-comment", + "arguments": [ + "" + ] }, { - name: 'doc-comment', - arguments: [' * Reads: `1`'], + "name": "doc-comment", + "arguments": [ + " * Reads: `1`" + ] }, { - name: 'doc-comment', - arguments: [' * Writes: `1`'], + "name": "doc-comment", + "arguments": [ + " * Writes: `1`" + ] }, { - name: 'storage', - arguments: ['write'], - }, - ], - }, + "name": "storage", + "arguments": [ + "write" + ] + } + ] + } ], - loggedTypes: [ + "loggedTypes": [ { - logId: '4571204900286667806', - concreteTypeId: '3f702ea3351c9c1ece2b84048006c8034a24cbc2bad2e740d0412b4172951d3d', + "logId": "4571204900286667806", + "concreteTypeId": "3f702ea3351c9c1ece2b84048006c8034a24cbc2bad2e740d0412b4172951d3d" }, { - logId: '2151606668983994881', - concreteTypeId: '1ddc0adda1270a016c08ffd614f29f599b4725407c8954c8b960bdf651a9a6c8', + "logId": "2151606668983994881", + "concreteTypeId": "1ddc0adda1270a016c08ffd614f29f599b4725407c8954c8b960bdf651a9a6c8" }, { - logId: '2161305517876418151', - concreteTypeId: '1dfe7feadc1d9667a4351761230f948744068a090fe91b1bc6763a90ed5d3893', + "logId": "2161305517876418151", + "concreteTypeId": "1dfe7feadc1d9667a4351761230f948744068a090fe91b1bc6763a90ed5d3893" }, { - logId: '4354576968059844266', - concreteTypeId: '3c6e90ae504df6aad8b34a93ba77dc62623e00b777eecacfa034a8ac6e890c74', + "logId": "4354576968059844266", + "concreteTypeId": "3c6e90ae504df6aad8b34a93ba77dc62623e00b777eecacfa034a8ac6e890c74" }, { - logId: '10870989709723147660', - concreteTypeId: '96dd838b44f99d8ccae2a7948137ab6256c48ca4abc6168abc880de07fba7247', + "logId": "10870989709723147660", + "concreteTypeId": "96dd838b44f99d8ccae2a7948137ab6256c48ca4abc6168abc880de07fba7247" }, { - logId: '10098701174489624218', - concreteTypeId: '8c25cb3686462e9a86d2883c5688a22fe738b0bbc85f458d2d2b5f3f667c6d5a', - }, + "logId": "10098701174489624218", + "concreteTypeId": "8c25cb3686462e9a86d2883c5688a22fe738b0bbc85f458d2d2b5f3f667c6d5a" + } ], - messagesTypes: [], - configurables: [ + "messagesTypes": [], + "configurables": [ { - name: 'INITIAL_TARGET', - concreteTypeId: '0d79387ad3bacdc3b7aad9da3a96f4ce60d9a1b6002df254069ad95a3931d5c8', - offset: 13368, + "name": "INITIAL_TARGET", + "concreteTypeId": "0d79387ad3bacdc3b7aad9da3a96f4ce60d9a1b6002df254069ad95a3931d5c8", + "offset": 13368 }, { - name: 'INITIAL_OWNER', - concreteTypeId: '192bc7098e2fe60635a9918afb563e4e5419d386da2bdbf0d716b4bc8549802c', - offset: 13320, - }, - ], + "name": "INITIAL_OWNER", + "concreteTypeId": "192bc7098e2fe60635a9918afb563e4e5419d386da2bdbf0d716b4bc8549802c", + "offset": 13320 + } + ] }; const storageSlots: StorageSlot[] = [ { - key: '7bb458adc1d118713319a5baa00a2d049dd64d2916477d2688d76970c898cd55', - value: '0000000000000000000000000000000000000000000000000000000000000000', + "key": "7bb458adc1d118713319a5baa00a2d049dd64d2916477d2688d76970c898cd55", + "value": "0000000000000000000000000000000000000000000000000000000000000000" }, { - key: '7bb458adc1d118713319a5baa00a2d049dd64d2916477d2688d76970c898cd56', - value: '0000000000000000000000000000000000000000000000000000000000000000', + "key": "7bb458adc1d118713319a5baa00a2d049dd64d2916477d2688d76970c898cd56", + "value": "0000000000000000000000000000000000000000000000000000000000000000" }, { - key: 'bb79927b15d9259ea316f2ecb2297d6cc8851888a98278c0a2e03e1a091ea754', - value: '0000000000000000000000000000000000000000000000000000000000000000', + "key": "bb79927b15d9259ea316f2ecb2297d6cc8851888a98278c0a2e03e1a091ea754", + "value": "0000000000000000000000000000000000000000000000000000000000000000" }, { - key: 'bb79927b15d9259ea316f2ecb2297d6cc8851888a98278c0a2e03e1a091ea755', - value: '0000000000000000000000000000000000000000000000000000000000000000', - }, -]; - -export class Src14OwnedProxyInterface extends Interface { - constructor() { - super(abi); + "key": "bb79927b15d9259ea316f2ecb2297d6cc8851888a98278c0a2e03e1a091ea755", + "value": "0000000000000000000000000000000000000000000000000000000000000000" } +] +; +export class Src14OwnedProxyAbiCoder extends AbiCoder { declare functions: { - proxy_target: FunctionFragment; - set_proxy_target: FunctionFragment; - proxy_owner: FunctionFragment; - initialize_proxy: FunctionFragment; - set_proxy_owner: FunctionFragment; + proxy_target: AbiCoderFunction; + set_proxy_target: AbiCoderFunction; + proxy_owner: AbiCoderFunction; + initialize_proxy: AbiCoderFunction; + set_proxy_owner: AbiCoderFunction; }; } @@ -671,7 +802,7 @@ export class Src14OwnedProxy extends Contract { static readonly abi = abi; static readonly storageSlots = storageSlots; - declare interface: Src14OwnedProxyInterface; + declare interface: Src14OwnedProxyAbiCoder; declare functions: { proxy_target: InvokeFunction<[], Option>; set_proxy_target: InvokeFunction<[new_target: ContractIdInput], void>; @@ -680,7 +811,10 @@ export class Src14OwnedProxy extends Contract { set_proxy_owner: InvokeFunction<[new_proxy_owner: StateInput], void>; }; - constructor(id: string | AbstractAddress, accountOrProvider: Account | Provider) { + constructor( + id: string | AbstractAddress, + accountOrProvider: Account | Provider, + ) { super(id, abi, accountOrProvider); } } diff --git a/packages/recipes/src/types/Src14OwnedProxyFactory.ts b/packages/recipes/src/types/Src14OwnedProxyFactory.ts index b0789cb3f5a..4c730e1508d 100644 --- a/packages/recipes/src/types/Src14OwnedProxyFactory.ts +++ b/packages/recipes/src/types/Src14OwnedProxyFactory.ts @@ -8,23 +8,33 @@ Fuels version: 0.97.0 */ + + + + import { ContractFactory, type DeployContractOptions } from '@fuel-ts/contract'; import { decompressBytecode } from '@fuel-ts/utils'; -import { type Provider, type Account } from '@fuel-ts/account'; -import { Src14OwnedProxy } from './Src14OwnedProxy'; +import { type Provider, type Account } from '@fuel-ts/account';import { Src14OwnedProxy } from "./Src14OwnedProxy"; -const bytecode = decompressBytecode( - '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' -); +const bytecode = decompressBytecode("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"); export class Src14OwnedProxyFactory extends ContractFactory { + static readonly bytecode = bytecode; constructor(accountOrProvider: Account | Provider) { - super(bytecode, Src14OwnedProxy.abi, accountOrProvider, Src14OwnedProxy.storageSlots); + super( + bytecode, + Src14OwnedProxy.abi, + accountOrProvider, + Src14OwnedProxy.storageSlots + ); } - static deploy(wallet: Account, options: DeployContractOptions = {}) { + static deploy ( + wallet: Account, + options: DeployContractOptions = {} + ) { const factory = new Src14OwnedProxyFactory(wallet); return factory.deploy(options); } diff --git a/packages/recipes/src/types/common.d.ts b/packages/recipes/src/types/common.d.ts index d7ca20eaa69..4b26f59da49 100644 --- a/packages/recipes/src/types/common.d.ts +++ b/packages/recipes/src/types/common.d.ts @@ -28,4 +28,4 @@ export type Vec = T[]; * Mimics Sway Result enum type. * Ok represents the success case, while Err represents the error case. */ -export type Result = Enum<{ Ok: T; Err: E }>; +export type Result = Enum<{Ok: T, Err: E}>; diff --git a/packages/script/package.json b/packages/script/package.json index ead7b90d01f..17f653f9f69 100644 --- a/packages/script/package.json +++ b/packages/script/package.json @@ -27,7 +27,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@fuel-ts/abi-coder": "workspace:*", + "@fuel-ts/abi": "workspace:*", "@fuel-ts/account": "workspace:*", "@fuel-ts/address": "workspace:*", "@fuel-ts/errors": "workspace:*", diff --git a/packages/script/src/script.test.ts b/packages/script/src/script.test.ts index 069c02c0429..6dfff9c65c9 100644 --- a/packages/script/src/script.test.ts +++ b/packages/script/src/script.test.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import type { JsonAbi } from '@fuel-ts/abi-coder'; -import { Interface } from '@fuel-ts/abi-coder'; +import type { AbiSpecification } from '@fuel-ts/abi'; +import { AbiCoder } from '@fuel-ts/abi'; import type { Account, TransactionResponse, TransactionResult } from '@fuel-ts/account'; import { ScriptTransactionRequest } from '@fuel-ts/account'; import { setupTestProviderAndWallets } from '@fuel-ts/account/test-utils'; @@ -61,7 +61,7 @@ type MyStruct = { describe('Script', () => { let scriptRequest: ScriptRequest; beforeAll(() => { - const abiInterface = new Interface(scriptJsonAbi); + const abiInterface = AbiCoder.fromAbi(scriptJsonAbi); scriptRequest = new ScriptRequest( scriptBin, (myStruct: MyStruct) => { @@ -77,8 +77,7 @@ describe('Script', () => { throw new Error('fail'); } - const decoded = abiInterface.functions.main.decodeOutput(scriptResult.returnReceipt.data); - return (decoded as any)[0]; + return abiInterface.functions.main.decodeOutput(scriptResult.returnReceipt.data) as any; } ); }); @@ -145,7 +144,7 @@ describe('Script', () => { wallets: [wallet], } = launched; - const jsonAbiWithConfigurablesMock: JsonAbi = { + const jsonAbiWithConfigurablesMock: AbiSpecification = { ...jsonAbiMock, configurables: [ { diff --git a/packages/script/src/script.ts b/packages/script/src/script.ts index 765ce671e51..e69788540fe 100644 --- a/packages/script/src/script.ts +++ b/packages/script/src/script.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { Interface } from '@fuel-ts/abi-coder'; -import type { InputValue, JsonAbi } from '@fuel-ts/abi-coder'; +import { AbiCoder } from '@fuel-ts/abi'; +import type { InputValue, AbiSpecification } from '@fuel-ts/abi'; import { deployScriptOrPredicate, type Account, type Provider } from '@fuel-ts/account'; import { FuelError } from '@fuel-ts/errors'; import { AbstractScript } from '@fuel-ts/interfaces'; @@ -38,7 +38,7 @@ export class Script, TOutput> extends AbstractScript { /** * The ABI interface for the script. */ - interface: Interface; + interface: AbiCoder; /** * The account associated with the script. @@ -67,10 +67,10 @@ export class Script, TOutput> extends AbstractScript { * @param abi - The ABI interface for the script. * @param account - The account associated with the script. */ - constructor(bytecode: BytesLike, abi: JsonAbi, account: Account) { + constructor(bytecode: BytesLike, abi: AbiSpecification, account: Account) { super(); this.bytes = arrayify(bytecode); - this.interface = new Interface(abi); + this.interface = AbiCoder.fromAbi(abi); this.provider = account.provider; this.account = account; @@ -107,7 +107,7 @@ export class Script, TOutput> extends AbstractScript { const { offset } = this.interface.configurables[key]; - const encoded = this.interface.encodeConfigurable(key, value as InputValue); + const encoded = this.interface.getConfigurable(key).encode(value as InputValue); this.bytes.set(encoded, offset); }); @@ -133,7 +133,7 @@ export class Script, TOutput> extends AbstractScript { deploy(account: Account) { return deployScriptOrPredicate({ deployer: account, - abi: this.interface.jsonAbi, + abi: this.interface.specification, bytecode: this.bytes, loaderInstanceCallback: (loaderBytecode, newAbi) => new Script(loaderBytecode, newAbi, this.account) as T, diff --git a/packages/script/test/fixtures/index.ts b/packages/script/test/fixtures/index.ts index ed64d80e4ac..16eb2bf5433 100644 --- a/packages/script/test/fixtures/index.ts +++ b/packages/script/test/fixtures/index.ts @@ -1,4 +1,4 @@ -import type { JsonAbi } from '@fuel-ts/abi-coder'; +import type { AbiSpecification } from '@fuel-ts/abi'; import { getForcProject } from '@fuel-ts/utils/test-utils'; import { join } from 'path'; @@ -7,7 +7,7 @@ export enum ScriptProjectsEnum { } export const getScriptForcProject = (project: ScriptProjectsEnum) => - getForcProject({ + getForcProject({ projectDir: join(__dirname, 'forc-projects', project), projectName: project, build: 'release', diff --git a/packages/script/test/mocks.ts b/packages/script/test/mocks.ts index 61df118fbb5..b98de2a37b4 100644 --- a/packages/script/test/mocks.ts +++ b/packages/script/test/mocks.ts @@ -1,6 +1,6 @@ -import type { JsonAbi } from '@fuel-ts/abi-coder'; +import type { AbiSpecification } from '@fuel-ts/abi'; -export const jsonAbiMock: JsonAbi = { +export const jsonAbiMock: AbiSpecification = { programType: 'script', specVersion: '1', encodingVersion: '1', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 76dc246830c..e484cb07ebd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -637,12 +637,27 @@ importers: packages/abi: dependencies: + '@fuel-ts/crypto': + specifier: workspace:* + version: link:../crypto '@fuel-ts/errors': specifier: workspace:* version: link:../errors + '@fuel-ts/hasher': + specifier: workspace:* + version: link:../hasher + '@fuel-ts/interfaces': + specifier: workspace:* + version: link:../interfaces + '@fuel-ts/math': + specifier: workspace:* + version: link:../math '@fuel-ts/utils': specifier: workspace:* version: link:../utils + type-fest: + specifier: ^4.26.1 + version: 4.26.1 packages/abi-coder: dependencies: @@ -713,9 +728,9 @@ importers: packages/account: dependencies: - '@fuel-ts/abi-coder': + '@fuel-ts/abi': specifier: workspace:* - version: link:../abi-coder + version: link:../abi '@fuel-ts/address': specifier: workspace:* version: link:../address @@ -816,9 +831,9 @@ importers: packages/contract: dependencies: - '@fuel-ts/abi-coder': + '@fuel-ts/abi': specifier: workspace:* - version: link:../abi-coder + version: link:../abi '@fuel-ts/account': specifier: workspace:* version: link:../account @@ -951,6 +966,9 @@ importers: packages/fuels: dependencies: + '@fuel-ts/abi': + specifier: workspace:* + version: link:../abi '@fuel-ts/abi-coder': specifier: workspace:* version: link:../abi-coder @@ -1111,9 +1129,9 @@ importers: packages/program: dependencies: - '@fuel-ts/abi-coder': + '@fuel-ts/abi': specifier: workspace:* - version: link:../abi-coder + version: link:../abi '@fuel-ts/account': specifier: workspace:* version: link:../account @@ -1148,9 +1166,9 @@ importers: packages/recipes: dependencies: - '@fuel-ts/abi-coder': + '@fuel-ts/abi': specifier: workspace:* - version: link:../abi-coder + version: link:../abi '@fuel-ts/abi-typegen': specifier: workspace:* version: link:../abi-typegen @@ -1175,9 +1193,9 @@ importers: packages/script: dependencies: - '@fuel-ts/abi-coder': + '@fuel-ts/abi': specifier: workspace:* - version: link:../abi-coder + version: link:../abi '@fuel-ts/account': specifier: workspace:* version: link:../account @@ -6875,9 +6893,6 @@ packages: async@3.2.5: resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==} - async@3.2.6: - resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} - asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -13018,7 +13033,6 @@ packages: engines: {node: '>=0.6.0', teleport: '>=0.2.0'} deprecated: |- You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. - (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) qr-code-styling@1.6.0-rc.1: @@ -24727,8 +24741,6 @@ snapshots: async@3.2.5: {} - async@3.2.6: {} - asynckit@0.4.0: {} at-least-node@1.0.0: {} @@ -27265,7 +27277,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) @@ -27320,7 +27332,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 @@ -27430,7 +27442,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 @@ -29487,7 +29499,7 @@ snapshots: jake@10.8.7: dependencies: - async: 3.2.6 + async: 3.2.5 chalk: 4.1.2 filelist: 1.0.4 minimatch: 3.1.2 @@ -30638,7 +30650,7 @@ snapshots: markdown-link-check@3.12.2: dependencies: - async: 3.2.6 + async: 3.2.5 chalk: 5.3.0 commander: 12.1.0 link-check: 5.4.0 diff --git a/tsconfig.test.json b/tsconfig.test.json index 96ad5020fb9..a585e54f1bd 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -1,8 +1,9 @@ { "extends": "./tsconfig.base.json", "compilerOptions": { - "noEmit": true + "noEmit": true, + "types": ["./vitest.d.ts"] }, - "include": ["**/*.test.ts"], + "include": ["**/*.test.ts", "vitest.**/*.ts"], "exclude": ["node_modules", "apps/docs/src/**/*.test.ts"] } diff --git a/vitest.d.ts b/vitest.d.ts new file mode 100644 index 00000000000..1ceab1a4a1c --- /dev/null +++ b/vitest.d.ts @@ -0,0 +1,11 @@ +import 'vitest'; + +interface CustomMatchers { + toEqualBn: () => R; +} + +declare module 'vitest' { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + interface Assertion extends CustomMatchers {} + interface AsymmetricMatchersContaining extends CustomMatchers {} +}