Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: ABI coder #3402

Open
wants to merge 16 commits into
base: ns/feat/abi-parser
Choose a base branch
from
Open

Conversation

petertonysmith94
Copy link
Contributor

@petertonysmith94 petertonysmith94 commented Nov 18, 2024

Summary

  • This PR encompasses the refactoring and revamping of the encoding.
  • The following diagram outlines all the different encoding types and how the encoded types look in bytes.

image

Breaking Changes

Note

These will need to be combined into the final PR (#3085)

  • Accessing underlying coders is now achieved through a convenient class, AbiEncoding.
// Before
import { NumberCoder, BigNumberCoder, B256Coder, B512Coder, VoidCoder, BooleanCoder, RawSliceCoder, StrSliceCoder, StdStringCoder } from '@fuel-ts/abi-coder';

const u8Coder = new NumberCoder('u8');
const u16Coder = new NumberCoder('u16');
const u32Coder = new NumberCoder('u32');
const u64Coder = new BigNumberCoder('u64');
const u256Coder = new BigNumberCoder('u256');
const b256Coder = new B256Coder();
const b512Coder = new B512Coder();
const boolCoder = new BooleanCoder();
const voidCoder = new VoidCoder();

const byteCoder = new ByteCoder();
const rawSliceCoder = new RawSliceCoder();
const strSliceCoder = new StrSliceCoder();
const stdStringCoder = new StdStringCoder();
const stringCoder = new StringCoder(4);

const arrayCoder = new ArrayCoder(u8Coder, 3);
const tupleCoder = new TupleCoder([u8Coder, u8Coder]);
const vectorCoder = new VecCoder(u8Coder);

const enumCoder = new EnumCoder('name of enum', { u8: u8Coder });
const optionCoder = new OptionCoder('name of option', { None: voidCoder, Some: voidCoder });
const structCoder = new StructCoder('name of struct', { u8: u8Coder });
// After
import { encoding } from '@fuel-ts/abi';

const u8Coder = encoding.u8;
const u16Coder = encoding.u16;
const u32Coder = encoding.u32;
const u64Coder = encoding.u64;
const u256Coder = encoding.u256;
const b256Coder = encoding.b256;
const b512Coder = encoding.b512;
const boolCoder = encoding.bool;
const voidCoder = encoding.void;

const byteCoder = encoding.byte;
const rawSliceCoder = encoding.rawSlice;
const strSliceCoder = encoding.str;
const stdStringCoder = encoding.stdString;
const stringCoder = encoding.string(4);

const arrayCoder = encoding.array(u8Coder, 3);
const tupleCoder = encoding.tuple([u8Coder, u8Coder]);
const vectorCoder = encoding.vector(u8Coder);

const enumCoder = encoding.enum({ u8: u8Coder });
const optionCoder = encoding.option({ None: voidCoder, Some: voidCoder });
const structCoder = encoding.struct({ u8: u8Coder });
  • Removal of the constant INPUT_COIN_FIXED_SIZE
import { INPUT_COIN_FIXED_SIZE } from 'fuels';
  • Renamed Encoding from the @fuel-ts/crypto package to BufferEncoding.
// Before
import { Encoding } from '@fuel-ts/crypto';

// After
import { BufferEncoding } from '@fuel-ts/crypto';

In progress

Ref

Todo tests

  • types_struct_with_complex_nested_struct
  • types_struct_with_array

Document

  • createFunctionSelector what is the selector used for and how is it used?
  • Breakdown the specification + parsing of the ABI to allow for easier to consume encoding + decoding.
    • What is a concrete and metadata type, what are the differences?
    • How does the ABI specification concretely tell us how to encode/decode a type?
    • General flow of the abi package.

Refactor:

  • Separate logic of the native and enum coders.
  • Separate the fromAbi functionationlity away from the coders.
  • Abstract the assertions outside of the coders.
  • Remove utils from the AbiEncoding class.
  • Remove static accessors for coders; this is not tree-shakable.

Checklist

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

@petertonysmith94 petertonysmith94 added the feat Issue is a feature label Nov 18, 2024
@petertonysmith94 petertonysmith94 self-assigned this Nov 18, 2024
Copy link

vercel bot commented Nov 18, 2024

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

Name Status Preview Comments Updated (UTC)
fuels-template ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jan 2, 2025 11:35am
ts-docs ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jan 2, 2025 11:35am
ts-docs-api ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jan 2, 2025 11:35am

Copy link

codspeed-hq bot commented Nov 25, 2024

CodSpeed Performance Report

Merging #3402 will degrade performances by 14.08%

Comparing ps/feat/abi-coder (3a6b861) with ns/feat/abi-parser (b6c5936)

Summary

⚡ 1 improvements
❌ 3 regressions
✅ 14 untouched benchmarks

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Benchmarks breakdown

Benchmark ns/feat/abi-parser ps/feat/abi-coder Change
should successfully get transaction cost estimate for a batch transfer (x20 times) 24.6 ms 28.7 ms -14.08%
should successfully get transaction cost estimate for a mint (x20 times) 21.9 ms 24.8 ms -11.61%
should successfully get transaction cost estimate for a single transfer (x20 times) 18.3 ms 21.1 ms -13.47%
should successfully conduct a custom transfer between wallets (x20 times) 96.9 ms 54.4 ms +78.02%

Copy link
Contributor

@nedsalk nedsalk left a comment

Choose a reason for hiding this comment

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

Looks good.

I tested out encoding an invalid value in a struct field and the error thrown is a one-liner without much details. Did you plan on doing anything related to pretty error message printing here or will that be in a subsequent PR?


const abiInterface = new Interface(abi);
const abiInterface = AbiCoder.fromAbi(abi);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
const abiInterface = AbiCoder.fromAbi(abi);
const abiCoder = AbiCoder.fromAbi(abi);

And all the ones below that'll fail to compile because of this renaming.

*
* @throws FuelError - when the function is not found
*/
public getFunction(nameOrSignatureOrSelector: string): AbiCoderFunction {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure that we even need this function anymore. We can possibly reopen #1111 and remove the function. Those who want to get a function by name then can do:

const myFn = abiCoder.functions['myfn'];

This would also remove the need for functionLookup.

*
* @throws FuelError - when the configurable is not found
*/
public getConfigurable(name: string): AbiCoderConfigurable {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd vote for removing this method as just doing abiCoder.configurables[name] is enough.

*
* @throws FuelError - when the log is not found
*/
public getLog(logId: string): AbiCoderLog {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd vote for removing this method as just doing abiCoder.logs[logId] is enough.

};

export const encoding: Encoding = {
...currentEncoding,
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure about this spread because adding support for a new encoding version which would then become the current one would imply a breaking change to behavior because some of the spread coders would be behaving differently. I'd rather have people access the encodings via encoding.v1.enum and encoding.v2.enum.


import type { SupportedCoder, SupportedCoders } from './encoding-types';

export const createCoderMatcher = (coders: SupportedCoders) =>
Copy link
Contributor

Choose a reason for hiding this comment

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

Given that the set of encoding versions is finite, we don't need to always recalculate this and can immediately create matchers for the supported encoding versions:

const v1Matcher = createMatcher<SupportedCoder | undefined>({u8: v1.u8, ...});

export const getCoderMatcher(version: string) {
  switch(version) {
    case "1":
      return v1Matcher;
  }
}

'1': v1,
};

export const encoding: Encoding = {
Copy link
Contributor

Choose a reason for hiding this comment

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

With regards to treeshakeability, I think it's better we don't export this encoding object but rather export the v1, v2, etc. encodings separately as having it this way will bundle all encodings together but the user might be only interested in v2.

// abi/src/coder/index.ts
export { v1 as encodingV1, v2 as encodingV2 } from './somewhere;

@@ -47,7 +47,7 @@ export class Predicate<
> extends Account {
bytes: Uint8Array;
predicateData: TData = [] as unknown as TData;
interface: Interface;
interface: AbiCoder;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
interface: AbiCoder;
abiCoder: AbiCoder;

and all the other places where this renaming would break them.

configurableConstants?: { [name: string]: unknown }
) {
let predicateBytes = arrayify(bytes);
const abiInterface: Interface = new Interface(jsonAbi);
const abiInterface: AbiCoder = AbiCoder.fromAbi(jsonAbi);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
const abiInterface: AbiCoder = AbiCoder.fromAbi(jsonAbi);
const abiCoder: AbiCoder = AbiCoder.fromAbi(jsonAbi);

@@ -28,14 +28,13 @@ export function getDecodedLogs<T = unknown>(
*/
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);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
const interfaceToUse = AbiCoder.fromAbi(externalAbis[receipt.id] || mainAbi);
const abiCoderToUse = AbiCoder.fromAbi(externalAbis[receipt.id] || mainAbi);

@@ -18,10 +19,10 @@ export interface FunctionCall {
}

export const getFunctionCall = ({ abi, receipt }: GetFunctionCallProps): FunctionCall => {
const abiInterface = new Interface(abi);
const abiInterface = AbiCoder.fromAbi(abi);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
const abiInterface = AbiCoder.fromAbi(abi);
const abiCoder = AbiCoder.fromAbi(abi);

@@ -54,7 +54,7 @@ export type DeployContractResult<TContract extends Contract = Contract> = {
*/
export default class ContractFactory<TContract extends Contract = Contract> {
bytecode: BytesLike;
interface: Interface;
interface: AbiCoder;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
interface: AbiCoder;
abiCoder: AbiCoder;

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

Successfully merging this pull request may close these issues.

Abi - Refactor / Coder
6 participants