Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat!: improve vector output support for contracts #1183

Merged
merged 51 commits into from
Aug 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
0334934
add vector mega tests
Aug 13, 2023
361cbcb
basic test
Aug 13, 2023
ff75e54
all numerics pass
Aug 13, 2023
f8b04cb
add complex types
Aug 13, 2023
d7ef951
remove stops
Aug 13, 2023
38caefd
remove untyped slice check
Aug 13, 2023
2c1aa65
check pointer
Aug 13, 2023
c2587d0
pass pointer info
Aug 13, 2023
48a445a
support dynamic output decoding
Aug 13, 2023
c934e61
use expected types
Aug 13, 2023
1769232
more tests
Aug 13, 2023
83deca5
add more tests
Aug 13, 2023
6c810dc
fix math
Aug 14, 2023
0a43447
revise
Aug 14, 2023
f76b167
add more tests
Aug 14, 2023
6610761
adjust
Aug 14, 2023
02b951e
add case
Aug 14, 2023
3093d12
relocate
Aug 14, 2023
cd9e892
revise returns
Aug 14, 2023
68247b1
add multicall test
Aug 14, 2023
3cbbf7c
rename and re-do
Aug 14, 2023
736d4be
reduce
Aug 14, 2023
2783194
add test
Aug 14, 2023
271d9de
refactor test
Aug 14, 2023
c6ebfaf
rm unused
Aug 14, 2023
32ea83a
reduce noise
Aug 14, 2023
99e7e13
more tests
Aug 14, 2023
6f1af5a
remove vector noise
Aug 14, 2023
5c85195
check for type
Aug 14, 2023
351eda3
skips
Aug 14, 2023
8e6c60e
cleanup
Aug 14, 2023
bf24821
comment out
Aug 14, 2023
13f813e
Merge branch 'master' into cm/issue-933-new
Aug 14, 2023
41240f8
adjust test
Aug 14, 2023
0c0f78c
adjust
Aug 14, 2023
62b1105
Merge branch 'master' into cm/issue-933-new
nedsalk Aug 16, 2023
3ce3149
Merge branch 'master' into cm/issue-933-new
Aug 21, 2023
055241c
pr tweaks
Aug 21, 2023
3b1d350
change test name
camsjams Aug 21, 2023
93ae5f4
feat!: remove old byte coder (#1201)
Aug 21, 2023
c3e92d4
Merge branch 'master' into cm/issue-933-new
Aug 21, 2023
7cea5de
test: improve testing of vector output (#1191)
nedsalk Aug 21, 2023
b11c01f
Merge branch 'master' into cm/issue-933-new
nedsalk Aug 22, 2023
7118a7d
rm undefined check
Aug 23, 2023
d0d10a5
use suggested syntax
Aug 23, 2023
9218add
rm default value
Aug 23, 2023
bc2d0dd
convert to private methods and properties
Aug 23, 2023
60fa61f
Merge branch 'master' into cm/issue-933-new
Aug 23, 2023
5dd1c9a
update
Aug 23, 2023
be3d989
fix type
Aug 23, 2023
4c7eecc
catch for potential empty output types, revert test change
Aug 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cyan-knives-attack.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fuel-ts/abi-coder": minor
---

removed byte coder
6 changes: 6 additions & 0 deletions .changeset/sweet-sheep-double.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@fuel-ts/abi-coder": minor
"@fuel-ts/program": minor
---

Added vector output
9 changes: 0 additions & 9 deletions packages/abi-coder/src/abi-coder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ 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';
Expand Down Expand Up @@ -61,8 +60,6 @@ export abstract class AbiCoder {
return new U64Coder();
case 'bool':
return new BooleanCoder();
case 'byte':
return new ByteCoder();
case 'b256':
return new B256Coder();
case 'struct B512':
Expand All @@ -78,12 +75,6 @@ export abstract class AbiCoder {
return new StringCoder(length);
}

if (['raw untyped slice'].includes(resolvedAbiType.type)) {
const length = 0;
const itemCoder = new U64Coder();
return new ArrayCoder(itemCoder, length);
}

// ABI types underneath MUST have components by definition
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const components = resolvedAbiType.components!;
Expand Down
68 changes: 0 additions & 68 deletions packages/abi-coder/src/coders/byte.test.ts

This file was deleted.

31 changes: 0 additions & 31 deletions packages/abi-coder/src/coders/byte.ts

This file was deleted.

16 changes: 12 additions & 4 deletions packages/abi-coder/src/coders/vec.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Uint8ArrayWithDynamicData } from '../utilities';

import { BooleanCoder } from './boolean';
import { NumberCoder } from './number';
import { VecCoder } from './vec';

describe('VecCoder', () => {
Expand All @@ -26,10 +27,17 @@ describe('VecCoder', () => {
}).toThrow('expected array value');
});

it('should throw an error when decoding', () => {
const coder = new VecCoder(new BooleanCoder());
const invalidInput = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]);
it('should decode a u8 Vec', () => {
const coder = new VecCoder(new NumberCoder('u8'));
const input = new Uint8Array([
0, 0, 0, 0, 0, 0, 41, 16, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0,
8, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 7,
]);
const expected = [8, 6, 7];

expect(() => coder.decode(invalidInput, 0)).toThrow('unexpected Vec decode');
const [actual, newOffset] = coder.decode(input, 0);

expect(actual).toEqual(expected);
expect(newOffset).toEqual(24);
});
});
19 changes: 16 additions & 3 deletions packages/abi-coder/src/coders/vec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { bn } from '@fuel-ts/math';

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

import type { TypesOfCoder } from './abstract-coder';
import { Coder } from './abstract-coder';
Expand Down Expand Up @@ -44,7 +46,18 @@ export class VecCoder<TCoder extends Coder> extends Coder<
return concatWithDynamicData(parts);
}

decode(_data: Uint8Array, _offset: number): [DecodedValueOf<TCoder>, number] {
this.throwError('unexpected Vec decode', 'not implemented');
decode(data: Uint8Array, offset: number): [DecodedValueOf<TCoder>, number] {
const len = data.slice(16, 24);
const length = bn(new U64Coder().decode(len, 0)[0]).toNumber();
const vectorRawData = data.slice(
BASE_VECTOR_OFFSET,
BASE_VECTOR_OFFSET + length * this.coder.encodedLength
);
return [
chunkByLength(vectorRawData, this.coder.encodedLength).map(
(chunk) => this.coder.decode(chunk, 0)[0]
),
offset + BASE_VECTOR_OFFSET,
];
}
}
43 changes: 34 additions & 9 deletions packages/abi-coder/src/function-fragment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type { DecodedValue, InputValue } from './coders/abstract-coder';
import type { ArrayCoder } from './coders/array';
import { TupleCoder } from './coders/tuple';
import type { U64Coder } from './coders/u64';
import { VecCoder } from './coders/vec';
import { OPTION_CODER_TYPE } from './constants';
import type {
JsonAbi,
Expand All @@ -20,7 +21,7 @@ import type {
} from './json-abi';
import { ResolvedAbiType } from './resolved-abi-type';
import type { Uint8ArrayWithDynamicData } from './utilities';
import { isPointerType, unpackDynamicData, findOrThrow } from './utilities';
import { isPointerType, unpackDynamicData, findOrThrow, isHeapType } from './utilities';

const logger = new Logger(versions.FUELS);

Expand All @@ -33,6 +34,12 @@ export class FunctionFragment<
readonly name: string;
readonly jsonFn: JsonAbiFunction;
readonly attributes: readonly JsonAbiFunctionAttribute[];
readonly isInputDataPointer: boolean;
readonly outputMetadata: {
isHeapType: boolean;
encodedLength: number;
};

private readonly jsonAbi: JsonAbi;

constructor(jsonAbi: JsonAbi, name: FnName) {
Expand All @@ -41,6 +48,11 @@ export class FunctionFragment<
this.name = name;
this.signature = FunctionFragment.getSignature(this.jsonAbi, this.jsonFn);
this.selector = FunctionFragment.getFunctionSelector(this.signature);
this.isInputDataPointer = this.#isInputDataPointer();
this.outputMetadata = {
isHeapType: this.#isOutputDataHeap(),
encodedLength: this.#getOutputEncodedLength(),
};

this.attributes = this.jsonFn.attributes ?? [];
}
Expand All @@ -58,14 +70,33 @@ export class FunctionFragment<
return bn(hashedFunctionSignature.slice(0, 10)).toHex(8);
}

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

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

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

return isHeapType(outputType?.type || '');
}

#getOutputEncodedLength(): number {
try {
const heapCoder = AbiCoder.getCoder(this.jsonAbi, this.jsonFn.output);
if (heapCoder instanceof VecCoder) {
return heapCoder.coder.encodedLength;
}

return heapCoder.encodedLength;
} catch (e) {
return 0;
}
}

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

Expand Down Expand Up @@ -134,14 +165,8 @@ export class FunctionFragment<
}

const result = nonEmptyInputs.reduce(
(obj: { decoded: unknown[]; offset: number }, input, currentIndex) => {
(obj: { decoded: unknown[]; offset: number }, input) => {
const coder = AbiCoder.getCoder(this.jsonAbi, input);
if (currentIndex === 0) {
const inputAbiType = findOrThrow(this.jsonAbi.types, (t) => t.typeId === input.type);
if (inputAbiType.type === 'raw untyped slice') {
(coder as ArrayCoder<U64Coder>).length = bytes.length / 8;
}
}
const [decodedValue, decodedValueByteSize] = coder.decode(bytes, obj.offset);

return {
Expand Down
1 change: 0 additions & 1 deletion packages/abi-coder/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ export { ArrayCoder } from './coders/array';
export { B256Coder } from './coders/b256';
export { B512Coder } from './coders/b512';
export { BooleanCoder } from './coders/boolean';
export { ByteCoder } from './coders/byte';
export { EnumCoder } from './coders/enum';
export { NumberCoder } from './coders/number';
export { StringCoder } from './coders/string';
Expand Down
14 changes: 8 additions & 6 deletions packages/abi-coder/src/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { BytesLike } from '@ethersproject/bytes';
import { concat, arrayify } from '@ethersproject/bytes';

import { U64Coder } from './coders/u64';
import { WORD_SIZE } from './constants';
import { VEC_CODER_TYPE, WORD_SIZE } from './constants';

export type DynamicData = {
[pointerIndex: number]: Uint8ArrayWithDynamicData;
Expand Down Expand Up @@ -87,7 +87,7 @@ export function unpackDynamicData(
return updatedResults;
}

/** useful for debugging
/**
* Turns:
Uint8Array(24) [
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 24
Expand All @@ -107,14 +107,14 @@ export function unpackDynamicData(
]
*
*/
export const chunkByWord = (data: Uint8Array): Uint8Array[] => {
export const chunkByLength = (data: Uint8Array, length = WORD_SIZE): Uint8Array[] => {
const chunks = [];
let offset = 0;
let chunk = data.slice(offset, offset + WORD_SIZE);
let chunk = data.slice(offset, offset + length);
while (chunk.length) {
chunks.push(chunk);
offset += WORD_SIZE;
chunk = data.slice(offset, offset + WORD_SIZE);
offset += length;
chunk = data.slice(offset, offset + length);
}

return chunks;
Expand All @@ -139,6 +139,8 @@ export const isPointerType = (type: string) => {
}
};

export const isHeapType = (type: string) => type === VEC_CODER_TYPE;

export function findOrThrow<T>(
arr: readonly T[],
predicate: (val: T) => boolean,
Expand Down
Loading