Skip to content

Commit

Permalink
Merge branch 'master' into ns/feat/subscribe-to-transaction-status-ch…
Browse files Browse the repository at this point in the history
…anges
  • Loading branch information
nedsalk authored Sep 25, 2023
2 parents ffe75af + 68390ac commit 247d38d
Show file tree
Hide file tree
Showing 19 changed files with 476 additions and 3 deletions.
5 changes: 5 additions & 0 deletions .changeset/twenty-eagles-lick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fuel-ts/abi-coder": minor
---

Add support for Bytes and RawSlice
7 changes: 7 additions & 0 deletions packages/abi-coder/src/abi-coder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import { ArrayCoder } from './coders/array';
import { B256Coder } from './coders/b256';
import { B512Coder } from './coders/b512';
import { BooleanCoder } from './coders/boolean';
import { ByteCoder } from './coders/byte';
import { EnumCoder } from './coders/enum';
import { NumberCoder } from './coders/number';
import { OptionCoder } from './coders/option';
import { RawSliceCoder } from './coders/raw-slice';
import { StringCoder } from './coders/string';
import { StructCoder } from './coders/struct';
import { TupleCoder } from './coders/tuple';
Expand All @@ -22,6 +24,7 @@ import {
tupleRegEx,
OPTION_CODER_TYPE,
VEC_CODER_TYPE,
BYTES_CODER_TYPE,
} from './constants';
import type { JsonAbi, JsonAbiArgument } from './json-abi';
import { ResolvedAbiType } from './resolved-abi-type';
Expand Down Expand Up @@ -56,12 +59,16 @@ export abstract class AbiCoder {
case 'u64':
case 'raw untyped ptr':
return new U64Coder();
case 'raw untyped slice':
return new RawSliceCoder();
case 'bool':
return new BooleanCoder();
case 'b256':
return new B256Coder();
case 'struct B512':
return new B512Coder();
case BYTES_CODER_TYPE:
return new ByteCoder();
default:
break;
}
Expand Down
47 changes: 47 additions & 0 deletions packages/abi-coder/src/coders/byte.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import type { Uint8ArrayWithDynamicData } from '../utilities';

import { ByteCoder } from './byte';

describe('ByteCoder', () => {
it('should encode a byte', () => {
const coder = new ByteCoder();
const expected: Uint8ArrayWithDynamicData = new Uint8Array([
0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 3,
]);
expected.dynamicData = {
0: new Uint8Array([1, 2, 3, 0, 0, 0, 0, 0]),
};

const actual = coder.encode([1, 2, 3]);

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

it('should encode a byte [full word]', () => {
const coder = new ByteCoder();
const expected: Uint8ArrayWithDynamicData = new Uint8Array([
0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 8,
]);
expected.dynamicData = {
0: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]),
};

const actual = coder.encode([1, 2, 3, 4, 5, 6, 7, 8]);

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

it('should decode a byte', () => {
const coder = new ByteCoder();
const input = new Uint8Array([
0, 0, 0, 0, 3, 255, 255, 225, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 10, 0, 1, 2, 3, 4,
5, 6, 7, 8, 9,
]);
const expected = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);

const [actual, newOffset] = coder.decode(input, 0);

expect(actual).toEqual(expected);
expect(newOffset).toEqual(24);
});
});
63 changes: 63 additions & 0 deletions packages/abi-coder/src/coders/byte.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { arrayify, concat } from '@ethersproject/bytes';
import { ErrorCode } from '@fuel-ts/errors';
import { bn } from '@fuel-ts/math';

import { WORD_SIZE } from '../constants';
import type { Uint8ArrayWithDynamicData } from '../utilities';
import { BASE_VECTOR_OFFSET, concatWithDynamicData } from '../utilities';

import { Coder } from './abstract-coder';
import { U64Coder } from './u64';

export class ByteCoder extends Coder<number[], Uint8Array> {
static memorySize = 1;
constructor() {
super('struct', 'struct Bytes', BASE_VECTOR_OFFSET);
}

encode(value: number[]): Uint8Array {
if (!Array.isArray(value)) {
this.throwError(ErrorCode.ENCODE_ERROR, `Expected array value.`);
}

const parts: Uint8Array[] = [];

// pointer (ptr)
const pointer: Uint8ArrayWithDynamicData = new U64Coder().encode(BASE_VECTOR_OFFSET);

// pointer dynamicData, encode the byte vector now and attach to its pointer
const data = this.#getPaddedData(value);
pointer.dynamicData = {
0: concatWithDynamicData([data]),
};

parts.push(pointer);

// capacity (cap)
parts.push(new U64Coder().encode(data.byteLength));

// length (len)
parts.push(new U64Coder().encode(value.length));

return concatWithDynamicData(parts);
}

#getPaddedData(value: number[]): Uint8Array {
const data: Uint8Array[] = [arrayify(value)];

const paddingLength = (WORD_SIZE - (value.length % WORD_SIZE)) % WORD_SIZE;
if (paddingLength) {
data.push(new Uint8Array(paddingLength));
}

return concat(data);
}

decode(data: Uint8Array, offset: number): [Uint8Array, number] {
const len = data.slice(16, 24);
const length = bn(new U64Coder().decode(len, 0)[0]).toNumber();
const byteData = data.slice(BASE_VECTOR_OFFSET, BASE_VECTOR_OFFSET + length * 8);

return [byteData, offset + BASE_VECTOR_OFFSET];
}
}
35 changes: 35 additions & 0 deletions packages/abi-coder/src/coders/raw-slice.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { BN } from '@fuel-ts/math';

import type { Uint8ArrayWithDynamicData } from '../utilities';

import { RawSliceCoder } from './raw-slice';

describe('RawSliceCoder', () => {
it('should encode a raw-slice', () => {
const coder = new RawSliceCoder();
const expected: Uint8ArrayWithDynamicData = new Uint8Array([
0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 24,
]);
expected.dynamicData = {
0: new Uint8Array([0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3]),
};

const actual = coder.encode([1, 2, 3]);

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

it('should decode a raw-slice', () => {
const coder = new RawSliceCoder();
const input = new Uint8Array([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0,
3, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0,
0, 7, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 9,
]);

const [actual, newOffset] = coder.decode(input, 0);

expect(actual.map((v: BN) => v.toNumber())).toStrictEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
expect(newOffset).toEqual(80);
});
});
47 changes: 47 additions & 0 deletions packages/abi-coder/src/coders/raw-slice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { ErrorCode } from '@fuel-ts/errors';
import type { BN } from '@fuel-ts/math';

import { WORD_SIZE } from '../constants';
import type { Uint8ArrayWithDynamicData } from '../utilities';
import { BASE_RAW_SLICE_OFFSET, concatWithDynamicData } from '../utilities';

import { Coder } from './abstract-coder';
import { ArrayCoder } from './array';
import { U64Coder } from './u64';

export class RawSliceCoder extends Coder<number[], BN[]> {
constructor() {
super('raw untyped slice', 'raw untyped slice', BASE_RAW_SLICE_OFFSET);
}

encode(value: number[]): Uint8Array {
if (!Array.isArray(value)) {
this.throwError(ErrorCode.ENCODE_ERROR, `Expected array value.`);
}

const parts: Uint8Array[] = [];
const coder = new U64Coder();

// pointer (ptr)
const pointer: Uint8ArrayWithDynamicData = new U64Coder().encode(BASE_RAW_SLICE_OFFSET);

// pointer dynamicData, encode the vector now and attach to its pointer
pointer.dynamicData = {
0: concatWithDynamicData(value.map((v) => coder.encode(v))),
};

parts.push(pointer);

// length (len)
parts.push(new U64Coder().encode(value.length * WORD_SIZE));

return concatWithDynamicData(parts);
}

decode(data: Uint8Array, offset: number): [BN[], number] {
const internalCoder = new ArrayCoder(new U64Coder(), data.length / 8);
const decoded = internalCoder.decode(data, offset);

return decoded;
}
}
4 changes: 4 additions & 0 deletions packages/abi-coder/src/function-fragment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { bn } from '@fuel-ts/math';
import { AbiCoder } from './abi-coder';
import type { DecodedValue, InputValue } from './coders/abstract-coder';
import type { ArrayCoder } from './coders/array';
import { ByteCoder } from './coders/byte';
import { TupleCoder } from './coders/tuple';
import type { U64Coder } from './coders/u64';
import { VecCoder } from './coders/vec';
Expand Down Expand Up @@ -87,6 +88,9 @@ export class FunctionFragment<
if (heapCoder instanceof VecCoder) {
return heapCoder.coder.encodedLength;
}
if (heapCoder instanceof ByteCoder) {
return ByteCoder.memorySize;
}

return heapCoder.encodedLength;
} catch (e) {
Expand Down
4 changes: 4 additions & 0 deletions packages/abi-coder/src/resolved-abi-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ export class ResolvedAbiType {
return 'rawptr';
}

if (this.type === 'raw untyped slice') {
return 'rawslice';
}

const strMatch = stringRegEx.exec(this.type)?.groups;
if (strMatch) {
return `str[${strMatch.length}]`;
Expand Down
3 changes: 3 additions & 0 deletions packages/abi-coder/src/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ export type Uint8ArrayWithDynamicData = Uint8Array & {
const VEC_PROPERTY_SPACE = 3; // ptr + cap + length
export const BASE_VECTOR_OFFSET = VEC_PROPERTY_SPACE * WORD_SIZE;

const RAW_SLICE_PROPERTY_SPACE = 2; // ptr + length
export const BASE_RAW_SLICE_OFFSET = RAW_SLICE_PROPERTY_SPACE * WORD_SIZE;

// this is a fork of @ethersproject/bytes:concat
// this collects individual dynamicData data and relocates it to top level
export function concatWithDynamicData(items: ReadonlyArray<BytesLike>): Uint8ArrayWithDynamicData {
Expand Down
27 changes: 27 additions & 0 deletions packages/abi-coder/test/interface.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,33 @@ describe('Abi interface', () => {
value: { arr: [B256_DECODED, B256_DECODED, B256_DECODED], tuple: [B256_DECODED, U8_MAX] },
encodedValue: [B256_ENCODED, B256_ENCODED, B256_ENCODED, B256_ENCODED, U8_MAX_ENCODED],
},
{
fn: exhaustiveExamplesInterface.functions.bytes,
title: '[struct Bytes]',
value: [[1, 2, 3]],
encodedValue: new Uint8Array([
0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 3, 1, 2, 3, 0, 0,
0, 0, 0,
]),
decodedTransformer: (decoded: unknown | undefined) => {
const data = (decoded as BN[]).slice(0, 3);
return Array.from(data);
},
decodedTransfoarmer: (decoded: unknown | undefined) => Array.from(decoded as Uint8Array),
},
{
fn: exhaustiveExamplesInterface.functions.raw_slice,
title: '[raw_slice]',
value: [[1, 2, 3]],
encodedValue: new Uint8Array([
0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3,
]),
decodedTransformer: (decoded: unknown | undefined) => {
const data = (decoded as BN[]).slice(2);
return data.map((v: BN) => v.toNumber());
},
},
{
fn: exhaustiveExamplesInterface.functions.tuple_as_param,
title: '[tuple] as param',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
contract;
use std::b512::B512;
use std::bytes::Bytes;

enum EnumWithGeneric<T> {
VariantOne: T,
Expand Down Expand Up @@ -132,7 +133,9 @@ abi MyContract {
fn struct_generic_simple(x: StructB<u8>) -> StructB<u8>;
fn struct_with_tuple(x: StructB<(bool, u64)>) -> StructB<(bool, u64)>;
fn struct_with_implicitGenerics(arg: StructWithImplicitGenerics<b256, u8>) -> StructWithImplicitGenerics<b256, u8>;

fn bytes(arg: Bytes) -> Bytes;
fn raw_slice(arg: raw_slice) -> raw_slice;

fn tuple_as_param(x: (u8, StructA<StructB<u64>, str[3]>)) -> (u8, StructA<StructB<u64>, str[3]>);
fn array_simple(x: [u8; 4]) -> [u8; 4];
fn array_struct(x: [SimpleStruct; 3]) -> [SimpleStruct; 3];
Expand Down Expand Up @@ -244,6 +247,13 @@ impl MyContract for Contract {
arg
}

fn bytes(arg: Bytes) -> Bytes {
arg
}
fn raw_slice(arg: raw_slice) -> raw_slice {
arg
}

fn two_u8_vectors(x: Vec<u8>, y: Vec<u8>) -> (Vec<u8>, Vec<u8>) {
(x, y)
}
Expand Down
2 changes: 2 additions & 0 deletions packages/fuel-gauge/fixtures/forc-projects/Forc.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ members = [
"advanced-logging-other-contract-abi",
"auth_testing_abi",
"auth_testing_contract",
"bytes",
"call-test-contract",
"configurable-contract",
"collision_in_fn_names",
Expand All @@ -25,6 +26,7 @@ members = [
"predicate-with-configurable",
"predicate-u32",
"predicate-vector-types",
"raw-slice",
"revert-error",
"script-main-args",
"script-main-return-struct",
Expand Down
3 changes: 3 additions & 0 deletions packages/fuel-gauge/fixtures/forc-projects/bytes/Forc.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[project]
license = "Apache-2.0"
name = "bytes"
Loading

0 comments on commit 247d38d

Please sign in to comment.