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: add Vec support as an Input #497

Merged
merged 25 commits into from
Sep 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions .changeset/shiny-ligers-eat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@fuel-ts/abi-coder": patch
"@fuel-ts/contract": patch
"@fuel-ts/providers": patch
---

Added vec support
186 changes: 185 additions & 1 deletion packages/abi-coder/src/abi-coder.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { hexlify } from '@ethersproject/bytes';
import { hexlify, concat } from '@ethersproject/bytes';
import { bn, toHex } from '@fuel-ts/math';

import AbiCoder from './abi-coder';
Expand Down Expand Up @@ -165,4 +165,188 @@ describe('AbiCoder', () => {
])
);
});

it('encodes vectors', () => {
const types = [
{
name: 'vector',
type: 'struct Vec',
components: [
{
name: 'buf',
type: 'struct RawVec',
components: [
{
name: 'ptr',
type: 'u64',
isParamType: true,
},
{
name: 'cap',
type: 'u64',
isParamType: true,
},
],
typeArguments: [
{
name: '',
type: 'u8',
isParamType: true,
},
],
isParamType: true,
},
{
name: 'len',
type: 'u64',
},
],
typeArguments: [
{
name: '',
type: 'u8',
isParamType: true,
},
],
isParamType: true,
},
];

const input = [36];
const encoded = abiCoder.encode(types, [input]);

const pointer = [0, 0, 0, 0, 0, 0, 0, 24];
const capacity = [0, 0, 0, 0, 0, 0, 0, input.length];
const length = [0, 0, 0, 0, 0, 0, 0, input.length];
const data = [0, 0, 0, 0, 0, 0, 0, input[0]];
const vecData = concat([pointer, capacity, length, data]);

const expected = hexlify(vecData);

expect(hexlify(encoded)).toBe(expected);
});

it('encodes vectors with multiple items', () => {
const types = [
{
name: 'vector',
type: 'struct Vec',
components: [
{
name: 'buf',
type: 'struct RawVec',
components: [
{
name: 'ptr',
type: 'u64',
isParamType: true,
},
{
name: 'cap',
type: 'u64',
isParamType: true,
},
],
typeArguments: [
{
name: '',
type: 'u64',
isParamType: true,
},
],
isParamType: true,
},
{
name: 'len',
type: 'u64',
},
],
typeArguments: [
{
name: '',
type: 'u64',
isParamType: true,
},
],
isParamType: true,
},
];

const input = [36, 42, 57];
const encoded = abiCoder.encode(types, [input]);

const pointer = [0, 0, 0, 0, 0, 0, 0, 24];
const capacity = [0, 0, 0, 0, 0, 0, 0, input.length];
const length = [0, 0, 0, 0, 0, 0, 0, input.length];
const data1 = [0, 0, 0, 0, 0, 0, 0, input[0]];
const data2 = [0, 0, 0, 0, 0, 0, 0, input[1]];
const data3 = [0, 0, 0, 0, 0, 0, 0, input[2]];
const vecData = concat([pointer, capacity, length, data1, data2, data3]);

const expected = hexlify(vecData);

expect(hexlify(encoded)).toBe(expected);
});

it('encodes vectors with multiple items [with offset]', () => {
const types = [
{
name: 'vector',
type: 'struct Vec',
components: [
{
name: 'buf',
type: 'struct RawVec',
components: [
{
name: 'ptr',
type: 'u64',
isParamType: true,
},
{
name: 'cap',
type: 'u64',
isParamType: true,
},
],
typeArguments: [
{
name: '',
type: 'u64',
isParamType: true,
},
],
isParamType: true,
},
{
name: 'len',
type: 'u64',
},
],
typeArguments: [
{
name: '',
type: 'u64',
isParamType: true,
},
],
isParamType: true,
},
];

const input = [36, 42, 57];
const encoded = abiCoder.encode(types, [input], 14440);

const pointer = [0, 0, 0, 0, 0, 0, 56, 128];
const capacity = [0, 0, 0, 0, 0, 0, 0, input.length];
const length = [0, 0, 0, 0, 0, 0, 0, input.length];
const data1 = [0, 0, 0, 0, 0, 0, 0, input[0]];
const data2 = [0, 0, 0, 0, 0, 0, 0, input[1]];
const data3 = [0, 0, 0, 0, 0, 0, 0, input[2]];
const vecData = concat([pointer, capacity, length, data1, data2, data3]);

const expected = hexlify(vecData);

expect(hexlify(encoded)).toBe(expected);
});
});
23 changes: 19 additions & 4 deletions packages/abi-coder/src/abi-coder.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// See: https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
import type { BytesLike } from '@ethersproject/bytes';
import { arrayify } from '@ethersproject/bytes';
import { concat, arrayify } from '@ethersproject/bytes';
import { Logger } from '@ethersproject/logger';

import type { DecodedValue, InputValue } from './coders/abstract-coder';
Expand All @@ -16,16 +16,18 @@ import StringCoder from './coders/string';
import StructCoder from './coders/struct';
import TupleCoder from './coders/tuple';
import U64Coder from './coders/u64';
import VecCoder from './coders/vec';
camsjams marked this conversation as resolved.
Show resolved Hide resolved
import {
arrayRegEx,
enumRegEx,
stringRegEx,
structRegEx,
tupleRegEx,
OPTION_CODER_TYPE,
VEC_CODER_TYPE,
} from './constants';
import type { JsonAbiFragmentType } from './json-abi';
import { filterEmptyParams, hasOptionTypes } from './utilities';
import { filterEmptyParams, getVectorAdjustments, hasOptionTypes } from './utilities';

const logger = new Logger(process.env.BUILD_VERSION || '~');

Expand Down Expand Up @@ -69,6 +71,15 @@ export default class AbiCoder {
return new StringCoder(length);
}

if (param.type === VEC_CODER_TYPE && Array.isArray(param.typeArguments)) {
const typeArgument = param.typeArguments[0];
if (!typeArgument) {
throw new Error('Expected Vec type to have a type argument');
}
const itemCoder = this.getCoder(typeArgument);
return new VecCoder(itemCoder);
}

const structMatch = structRegEx.exec(param.type)?.groups;
if (structMatch && Array.isArray(param.components)) {
const coders = param.components.reduce((obj, component) => {
Expand Down Expand Up @@ -103,7 +114,7 @@ export default class AbiCoder {
return logger.throwArgumentError('Invalid type', 'type', param.type);
}

encode(types: ReadonlyArray<JsonAbiFragmentType>, values: InputValue[]): Uint8Array {
encode(types: ReadonlyArray<JsonAbiFragmentType>, values: InputValue[], offset = 0): Uint8Array {
const nonEmptyTypes = filterEmptyParams(types);
const shallowCopyValues = values.slice();

Expand All @@ -120,8 +131,12 @@ export default class AbiCoder {
}

const coders = nonEmptyTypes.map((type) => this.getCoder(type));
const vectorData = getVectorAdjustments(coders, shallowCopyValues, offset);

const coder = new TupleCoder(coders);
return coder.encode(shallowCopyValues);
const results = coder.encode(shallowCopyValues);

return concat([results, concat(vectorData)]);
}

decode(types: ReadonlyArray<JsonAbiFragmentType>, data: BytesLike): DecodedValue[] | undefined {
Expand Down
5 changes: 5 additions & 0 deletions packages/abi-coder/src/coders/abstract-coder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export default abstract class Coder<TInput = unknown, TDecoded = unknown> {
readonly name: string;
readonly type: string;
readonly encodedLength: number;
offset?: number;

constructor(name: string, type: string, encodedLength: number) {
this.name = name;
Expand All @@ -48,6 +49,10 @@ export default abstract class Coder<TInput = unknown, TDecoded = unknown> {
throw new Error('unreachable');
}

setOffset(offset: number): void {
this.offset = offset;
}

abstract encode(value: TInput, length?: number): Uint8Array;

abstract decode(data: Uint8Array, offset: number, length?: number): [TDecoded, number];
Expand Down
59 changes: 59 additions & 0 deletions packages/abi-coder/src/coders/vec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { concat } from '@ethersproject/bytes';

import { WORD_SIZE } from '../constants';

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

const VEC_PROPERTY_SPACE = 3; // ptr + cap + length

type InputValueOf<TCoder extends Coder> = Array<TypesOfCoder<TCoder>['Input']>;
type DecodedValueOf<TCoder extends Coder> = Array<TypesOfCoder<TCoder>['Decoded']>;

export default class VecCoder<TCoder extends Coder> extends Coder<
InputValueOf<TCoder>,
DecodedValueOf<TCoder>
> {
coder: TCoder;

constructor(coder: TCoder) {
super('struct', `struct Vec`, 0);
this.coder = coder;
}

static getBaseOffset(): number {
return VEC_PROPERTY_SPACE * WORD_SIZE;
}

getEncodedVectorData(value: InputValueOf<TCoder>): Uint8Array {
if (!Array.isArray(value)) {
this.throwError('expected array value', value);
}

const encodedValues = Array.from(value).map((v) => this.coder.encode(v));
return concat(encodedValues);
}

encode(value: InputValueOf<TCoder>): Uint8Array {
if (!Array.isArray(value)) {
this.throwError('expected array value', value);
}

const parts: Uint8Array[] = [];
// pointer (ptr)
const pointer = this.offset || 0;
parts.push(new U64Coder().encode(pointer));
// capacity (cap)
parts.push(new U64Coder().encode(value.length));
// length (len)
parts.push(new U64Coder().encode(value.length));

return concat(parts);
}

decode(data: Uint8Array, offset: number): [DecodedValueOf<TCoder>, number] {
this.throwError('unexpected Vec decode', 'not implemented');
return [undefined as unknown as DecodedValueOf<TCoder>, offset];
}
}
27 changes: 27 additions & 0 deletions packages/abi-coder/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,34 @@
export const OPTION_CODER_TYPE = 'enum Option';
export const VEC_CODER_TYPE = 'struct Vec';
export const stringRegEx = /str\[(?<length>[0-9]+)\]/;
export const arrayRegEx = /\[(?<item>[\w\s\\[\]]+);\s*(?<length>[0-9]+)\]/;
export const structRegEx = /^struct (?<name>\w+)$/;
export const enumRegEx = /^enum (?<name>\w+)$/;
export const tupleRegEx = /^\((?<items>.*)\)$/;
export const genericRegEx = /^generic (?<name>\w+)$/;

export const WORD_SIZE = 8;
export const BYTES_32 = 32;
export const MAX_INPUTS = 255;
export const ASSET_ID_LEN = BYTES_32;
export const CONTRACT_ID_LEN = BYTES_32;

// VM_TX_MEMORY = 10240
export const VM_TX_MEMORY =
BYTES_32 + // Tx ID
WORD_SIZE + // Tx size
// Asset ID/Balance coin input pairs
MAX_INPUTS * (ASSET_ID_LEN + WORD_SIZE);

// TRANSACTION_SCRIPT_FIXED_SIZE = 112
export const TRANSACTION_SCRIPT_FIXED_SIZE =
WORD_SIZE + // Identifier
WORD_SIZE + // Gas price
WORD_SIZE + // Gas limit
WORD_SIZE + // Maturity
WORD_SIZE + // Script size
WORD_SIZE + // Script data size
WORD_SIZE + // Inputs size
WORD_SIZE + // Outputs size
WORD_SIZE + // Witnesses size
BYTES_32; // Receipts root
2 changes: 2 additions & 0 deletions packages/abi-coder/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ export { default as StringCoder } from './coders/string';
export { default as StructCoder } from './coders/struct';
export { default as TupleCoder } from './coders/tuple';
export { default as U64Coder } from './coders/u64';
export { default as VecCoder } from './coders/vec';
export * from './utilities';
export * from './fragments/fragment';
export { default as FunctionFragment } from './fragments/function-fragment';
export { default as Interface } from './interface';
export { default as AbiCoder } from './abi-coder';
export * from './json-abi';
export * from './constants';
Loading