Skip to content

Commit

Permalink
feat(abi): Update interface to handle array and enum contract argumen…
Browse files Browse the repository at this point in the history
…ts (#419)

* feat: Update interface to handle array and enum contract arguments

* Add b256 enum test

* fix: more enum tests
  • Loading branch information
QuinnLee authored Aug 1, 2022
1 parent e038ec0 commit 212d51c
Show file tree
Hide file tree
Showing 8 changed files with 255 additions and 14 deletions.
6 changes: 6 additions & 0 deletions .changeset/six-cherries-share.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@fuel-ts/abi-coder": minor
"@fuel-ts/contract": minor
---

Update interface to handle array and enum contract arguments
20 changes: 20 additions & 0 deletions packages/abi-coder/src/abi-coder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,26 @@ describe('AbiCoder', () => {
expect(decoded).toEqual([B256, B256]);
});

it('encodes and decodes arrays', () => {
const types = [
{
name: 'a',
type: '[u64; 3]',
components: [
{
name: '__array_element',
type: 'u64',
components: null,
},
],
},
];

const encoded = abiCoder.encode(types, [[1, 2, 3]]);

expect(hexlify(encoded)).toBe('0x000000000000000100000000000000020000000000000003');
});

it('encodes and decodes nested reference types', () => {
const types = [
{
Expand Down
16 changes: 8 additions & 8 deletions packages/abi-coder/src/abi-coder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import type { JsonAbiFragmentType } from './json-abi';
import { filterEmptyParams } from './utilities';

export const stringRegEx = /str\[(?<length>[0-9]+)\]/;
export const arrayRegEx = /\[(?<item>[\w\s]+);\s*(?<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>.*)\)$/;
Expand Down Expand Up @@ -46,13 +46,6 @@ export default class AbiCoder {
default:
}

const stringMatch = stringRegEx.exec(param.type)?.groups;
if (stringMatch) {
const length = parseInt(stringMatch.length, 10);

return new StringCoder(length);
}

const arrayMatch = arrayRegEx.exec(param.type)?.groups;
if (arrayMatch) {
const length = parseInt(arrayMatch.length, 10);
Expand All @@ -64,6 +57,13 @@ export default class AbiCoder {
return new ArrayCoder(itemCoder, length);
}

const stringMatch = stringRegEx.exec(param.type)?.groups;
if (stringMatch) {
const length = parseInt(stringMatch.length, 10);

return new StringCoder(length);
}

const structMatch = structRegEx.exec(param.type)?.groups;
if (structMatch && Array.isArray(param.components)) {
const coders = param.components.reduce((obj, component) => {
Expand Down
9 changes: 7 additions & 2 deletions packages/abi-coder/src/fragments/function-fragment.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { FormatTypes, ParamType } from '@ethersproject/abi';

import { arrayRegEx, structRegEx } from '../abi-coder';
import { arrayRegEx, enumRegEx, structRegEx } from '../abi-coder';
import type { JsonAbiFragment } from '../json-abi';

import { Fragment } from './fragment';
Expand All @@ -18,7 +18,12 @@ function formatOverride(this: ParamType, format?: string): string {

const arrayMatch = arrayRegEx.exec(this.type)?.groups;
if (arrayMatch) {
return `[${arrayMatch.item}; ${arrayMatch.length}]`;
return `a[${arrayMatch.item};${arrayMatch.length}]`;
}

const enumMatch = enumRegEx.exec(this.type)?.groups;
if (enumMatch) {
return `e${this.format(format)}`;
}
}

Expand Down
78 changes: 75 additions & 3 deletions packages/abi-coder/src/interface.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,42 @@ describe('Interface', () => {
expect(decoded.length).toEqual(1);
expect(decoded[0]).toEqual(42n);
});
it('can calculate the correct sighash for array string values', () => {
const fnFragment = FunctionFragment.fromObject({
type: 'function',
inputs: [
{
name: 'arg',
type: '[s[3]; 3]',
components: [
{
name: '__array_element',
type: 's[3]',
},
],
},
],
name: 'takes_array',
outputs: [
{
name: '',
type: '[s[3]; 2]',
components: [
{
name: '__array_element',
type: 's[3]',
},
],
},
],
});

expect(fnFragment.format()).toBe('takes_array(a[s[3];3])');
const sighash = Interface.getSighash(fnFragment);
expect(hexlify(sighash)).toEqual('0x00000000b80a1c57');
});

it('can calculate the correct sighash for array values', () => {
it('can calculate the correct sighash for array of u64 values', () => {
const fnFragment = FunctionFragment.fromObject({
type: 'function',
inputs: [
Expand Down Expand Up @@ -155,8 +189,46 @@ describe('Interface', () => {
},
],
});

expect(fnFragment.format()).toBe('takes_array(a[u16;3])');
const sighash = Interface.getSighash(fnFragment);
expect(hexlify(sighash)).toEqual('0x00000000101cbeb5');
});

it('can calculate the correct sighash for enum', () => {
const fnFragment = FunctionFragment.fromObject({
type: 'function',
inputs: [
{
name: 'enum_arg',
type: 'enum TestEnum',
components: [
{
name: 'Value',
type: 'bool',
components: null,
},
{
name: 'Data',
type: 'bool',
components: null,
},
],
},
],
name: 'take_enum',
outputs: [
{
name: '',
type: 'bool',
components: null,
},
],
});

expect(fnFragment.format()).toBe('take_enum(e(bool,bool))');
const sighash = Interface.getSighash(fnFragment);
expect(hexlify(sighash)).toEqual('0x00000000058734b9');
expect(hexlify(sighash)).toEqual('0x00000000424d6522');
});

it('can encode and decode function data with array values', () => {
Expand Down Expand Up @@ -191,7 +263,7 @@ describe('Interface', () => {
},
]);
expect(hexlify(functionInterface.encodeFunctionData('takes_array', [[1, 2, 3]]))).toEqual(
'0x00000000058734b90000000000000001000000000000000100000000000000020000000000000003'
'0x00000000101cbeb50000000000000001000000000000000100000000000000020000000000000003'
);
});

Expand Down
56 changes: 56 additions & 0 deletions packages/contract/src/__test__/call-test-contract/src/main.sw
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ use std::logging::log;
use std::context::{*, call_frames::*, registers::context_gas};
use std::contract_id::ContractId;

enum TestB256Enum {
Value: b256,
Data: b256,
}

enum TestBoolEnum {
Value: bool,
Data: bool,
}

enum TestStringEnum {
Value: str[3],
Data: str[3],
}

struct TestStruct {
a: bool,
b: u64,
Expand Down Expand Up @@ -35,6 +50,14 @@ abi TestContract {
fn return_context_amount() -> u64;
fn return_context_asset() -> b256;
fn return_context_gas() -> u64;
fn take_array_string_shuffle(a: [str[3];3]) -> [str[3];3];
fn take_array_string_return_single(a: [str[3];3]) -> [str[3];1];
fn take_array_string_return_single_element(a: [str[3];3]) -> str[3];
fn take_array_number(a: [u64;3]) -> u64;
fn take_array_boolean(a: [bool;3]) -> bool;
fn take_b256_enum(a: TestB256Enum) -> b256;
fn take_bool_enum(enum_arg: TestBoolEnum) -> bool;
fn take_string_enum(enum_arg: TestStringEnum) -> str[3];
}

impl TestContract for Contract {
Expand Down Expand Up @@ -93,4 +116,37 @@ impl TestContract for Contract {
fn return_context_gas() -> u64 {
context_gas()
}
fn take_array_string_shuffle(a: [str[3];3]) -> [str[3];3] {
[a[2], a[0], a[1]]
}
fn take_array_string_return_single(a: [str[3];3]) -> [str[3];1] {
[a[0]]
}
fn take_array_string_return_single_element(a: [str[3];3]) -> str[3] {
a[0]
}
fn take_array_number(a: [u64;3]) -> u64 {
a[0]
}
fn take_array_boolean(a: [bool;3]) -> bool {
a[0]
}
fn take_b256_enum(enum_arg: TestB256Enum) -> b256 {
let enum_arg = match enum_arg {
TestB256Enum::Value(val) => val, TestB256Enum::Data(val) => val,
};
enum_arg
}
fn take_bool_enum(enum_arg: TestBoolEnum) -> bool {
let enum_arg = match enum_arg {
TestBoolEnum::Value(val) => val, TestBoolEnum::Data(val) => val,
};
enum_arg
}
fn take_string_enum(enum_arg: TestStringEnum) -> str[3] {
let enum_arg = match enum_arg {
TestStringEnum::Value(val) => val, TestStringEnum::Data(val) => val,
};
enum_arg
}
}
82 changes: 82 additions & 0 deletions packages/contract/src/__test__/contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,4 +430,86 @@ describe('Contract', () => {
.call<bigint>();
}).rejects.toThrowError(`gasLimit(${gasLimit}) is lower than the required (${gasUsed})`);
});

it('calls array functions', async () => {
const contract = await setup();

const { value: arrayBoolean } = await contract.functions
.take_array_boolean([true, false, false])
.call();

expect(arrayBoolean).toEqual(true);

const { value: arrayNumber } = await contract.functions.take_array_number([1, 2, 3]).call();

expect(arrayNumber).toEqual(1n);

const { value: arrayReturnShuffle } = await contract.functions
.take_array_string_shuffle(['abc', 'efg', 'hij'])
.call();

expect(arrayReturnShuffle).toEqual(['hij', 'abc', 'efg']);

const { value: arrayReturnSingle } = await contract.functions
.take_array_string_return_single(['abc', 'efg', 'hij'])
.call();

expect(arrayReturnSingle).toEqual(['abc']);
});

it('calls enum functions', async () => {
const contract = await setup();

const { value: enumB256ReturnValue } = await contract.functions
.take_b256_enum({
Value: '0xd5579c46dfcc7f18207013e65b44e4cb4e2c2298f4ac457ba8f82743f31e930b',
})
.call();

expect(enumB256ReturnValue).toEqual(
'0xd5579c46dfcc7f18207013e65b44e4cb4e2c2298f4ac457ba8f82743f31e930b'
);

const { value: enumB256ReturnData } = await contract.functions
.take_b256_enum({
Data: '0x1111111111111111111111111111111111111111111111111111111111111111',
})
.call();

expect(enumB256ReturnData).toEqual(
'0x1111111111111111111111111111111111111111111111111111111111111111'
);

const { value: enumBoolReturnValue } = await contract.functions
.take_bool_enum({
Value: true,
})
.call();

expect(enumBoolReturnValue).toEqual(true);

const { value: enumBoolReturnData } = await contract.functions
.take_bool_enum({
Data: false,
})
.call();

expect(enumBoolReturnData).toEqual(false);

const { value: enumStrReturnValue } = await contract.functions
.take_string_enum({
Value: 'abc',
})
.call();

expect(enumStrReturnValue).toEqual('abc');

const { value: enumStrReturnData } = await contract.functions
.take_string_enum({
Data: 'efg',
})
.call();

expect(enumStrReturnData).toEqual('efg');
});
});
2 changes: 1 addition & 1 deletion packages/forc-bin/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"private": true,
"name": "forc-bin",
"version": "0.18.1",
"version": "0.19.0",
"description": "",
"author": "Fuel Labs <contact@fuel.sh> (https://fuel.network/)",
"typedocMain": "src/index.ts",
Expand Down

0 comments on commit 212d51c

Please sign in to comment.