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 simpler Option style #471

Merged
merged 9 commits into from
Sep 1, 2022
Merged
Show file tree
Hide file tree
Changes from 4 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
6 changes: 6 additions & 0 deletions .changeset/weak-dancers-tell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@fuel-ts/abi-coder": patch
"@fuel-ts/contract": patch
---

add option type improvement
5 changes: 4 additions & 1 deletion packages/abi-coder/src/abi-coder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ 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 StringCoder from './coders/string';
import StructCoder from './coders/struct';
import TupleCoder from './coders/tuple';
Expand Down Expand Up @@ -82,7 +83,9 @@ export default class AbiCoder {
obj[component.name] = this.getCoder(component);
return obj;
}, {});
return new EnumCoder(enumMatch.name, coders);
return param.type === 'enum Option'
Copy link
Contributor

Choose a reason for hiding this comment

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

I was trying to think of a better way to de-nest this but couldn't. My gut tells me we should refactor getCoder but probably not a blocker for this pr

? new OptionCoder(enumMatch.name, coders)
: new EnumCoder(enumMatch.name, coders);
}

const tupleMatch = tupleRegEx.exec(param.type)?.groups;
Expand Down
4 changes: 2 additions & 2 deletions packages/abi-coder/src/coders/enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import type { TypesOfCoder } from './abstract-coder';
import Coder from './abstract-coder';
import NumberCoder from './number';

type InputValueOf<TCoders extends Record<string, Coder>> = RequireExactlyOne<{
export type InputValueOf<TCoders extends Record<string, Coder>> = RequireExactlyOne<{
[P in keyof TCoders]: TypesOfCoder<TCoders[P]>['Input'];
}>;
type DecodedValueOf<TCoders extends Record<string, Coder>> = RequireExactlyOne<{
export type DecodedValueOf<TCoders extends Record<string, Coder>> = RequireExactlyOne<{
[P in keyof TCoders]: TypesOfCoder<TCoders[P]>['Decoded'];
}>;

Expand Down
34 changes: 34 additions & 0 deletions packages/abi-coder/src/coders/option.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type Coder from './abstract-coder';
import type { InputValueOf, DecodedValueOf } from './enum';
import EnumCoder from './enum';

type Option<T> = T | undefined;
type SwayOption<T> = { None: [] } | { Some: T };

export default class OptionCoder<TCoders extends Record<string, Coder>> extends EnumCoder<TCoders> {
encode(value: InputValueOf<TCoders>): Uint8Array {
const result = super.encode(this.toSwayOption(value) as unknown as InputValueOf<TCoders>);
return result;
}

toSwayOption(input: InputValueOf<TCoders>): SwayOption<unknown> {
if (input !== undefined) {
return { Some: input };
}

return { None: [] };
}

decode(data: Uint8Array, offset: number): [DecodedValueOf<TCoders>, number] {
const [decoded, newOffset] = super.decode(data, offset);
return [this.toOption(decoded) as DecodedValueOf<TCoders>, newOffset];
}

toOption(output?: DecodedValueOf<TCoders>): Option<unknown> {
if (output && 'Some' in output) {
return output.Some;
}

return undefined;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ describe('Coverage Contract', () => {
expect((await contractInstance.functions.get_contract_id().call()).value).toStrictEqual({
value: '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
});
expect((await contractInstance.functions.get_some_option_u8().call()).value).toEqual(113);
expect((await contractInstance.functions.get_none_option_u8().call()).value).toEqual(undefined);
});

it('should test u8 variable type', async () => {
Expand Down Expand Up @@ -164,7 +166,7 @@ describe('Coverage Contract', () => {
expect(value).toStrictEqual(INPUT);
});

it.only('should test enum < 8 byte variable type', async () => {
it('should test enum < 8 byte variable type', async () => {
const INPUT = { Empty: [] };
const { value } = await contractInstance.functions.echo_enum_small(INPUT).call();
expect(value).toStrictEqual(INPUT);
Expand All @@ -175,4 +177,27 @@ describe('Coverage Contract', () => {
const { value } = await contractInstance.functions.echo_enum_big(INPUT).call();
expect(value).toStrictEqual(INPUT);
});

it('should test Option<u8> type', async () => {
const INPUT = 187;
const { value } = await contractInstance.functions.echo_option_u8(INPUT).call();
expect(value).toStrictEqual(INPUT);
});

it('should test Option<u32> extraction', async () => {
const INPUT_SOME = 123;
const { value: Some } = await contractInstance.functions
.echo_option_extract_u32(INPUT_SOME)
.call();
expect(Some).toStrictEqual(INPUT_SOME);

const INPUT_NONE = undefined;
const { value: None } = await contractInstance.functions
.echo_option_extract_u32(INPUT_NONE)
camsjams marked this conversation as resolved.
Show resolved Hide resolved
.call();
expect(None).toStrictEqual(404);

const { value: NoneVoid } = await contractInstance.functions.echo_option_extract_u32().call();
expect(NoneVoid).toStrictEqual(404);
camsjams marked this conversation as resolved.
Show resolved Hide resolved
});
});
26 changes: 25 additions & 1 deletion packages/contract/src/__test__/coverage-contract/src/main.sw
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::*;
use core::*;
use std::storage::*;
use std::contract_id::ContractId;
use std::option::Option;

pub struct U8Struct {
i: u8,
Expand Down Expand Up @@ -41,7 +42,9 @@ abi CoverageContract {
fn get_large_array() -> [u32;2];
fn get_empty_enum() -> SmallEnum;
fn get_contract_id() -> ContractId;
fn echo_u8(input: u8) -> u8;
fn get_some_option_u8() -> Option<u8>;
fn get_none_option_u8() -> Option<u8>;
fn echo_u8(input: u8) -> u8;
fn echo_u16(input: u16) -> u16;
fn echo_u32(input: u32) -> u32;
fn echo_u64(input: u64) -> u64;
Expand All @@ -62,6 +65,8 @@ abi CoverageContract {
fn echo_struct_b256(input: B256Struct) -> B256Struct;
fn echo_enum_small(input: SmallEnum) -> SmallEnum;
fn echo_enum_big(input: BigEnum) -> BigEnum;
fn echo_option_u8(input: Option<u8>) -> Option<u8>;
fn echo_option_extract_u32(input: Option<u32>) -> u32;
}

impl CoverageContract for Contract {
Expand Down Expand Up @@ -106,6 +111,16 @@ impl CoverageContract for Contract {
~ContractId::from(id)
}

fn get_some_option_u8() -> Option<u8> {
let o:Option<u8> = Option::Some(113);
o
}

fn get_none_option_u8() -> Option<u8> {
let o:Option<u8> = Option::None;
o
}

fn echo_u8(input: u8) -> u8 {
input
}
Expand Down Expand Up @@ -169,4 +184,13 @@ impl CoverageContract for Contract {
fn echo_enum_big(input: BigEnum) -> BigEnum {
input
}
fn echo_option_u8(input: Option<u8>) -> Option<u8> {
input
}
fn echo_option_extract_u32(input: Option<u32>) -> u32 {
match input {
Option::Some(value) => value,
Option::None => 404u32,
}
}
}
18 changes: 9 additions & 9 deletions packages/contract/src/scripts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,15 @@ export const contractCallScript = new Script<ContractCall[], Uint8Array[]>(
fn_selector: new NumberCoder('u64').decode(functionSelector, 0)[0],
fn_arg: fnArg,
parameters: {
amount: call.amount ? { Some: BigInt(call.amount) } : { None: [] },
asset_id: call.assetId ? { Some: { value: call.assetId } } : { None: [] },
gas: call.gas ? { Some: BigInt(call.gas) } : { None: [] },
amount: call.amount ? BigInt(call.amount) : undefined,
asset_id: call.assetId ? { value: call.assetId } : undefined,
gas: call.gas ? BigInt(call.gas) : undefined,
},
};

scriptCallSlot = { Some: scriptCall };
scriptCallSlot = scriptCall;
} else {
scriptCallSlot = { None: [] };
scriptCallSlot = undefined;
}

scriptCallSlots.push(scriptCallSlot);
Expand Down Expand Up @@ -100,12 +100,12 @@ export const contractCallScript = new Script<ContractCall[], Uint8Array[]>(

const contractCallResults: any[] = [];
(scriptReturn.call_returns as any[]).forEach((callResult, i) => {
if (callResult.Some) {
if (callResult.Some.Data) {
const [offset, length] = callResult.Some.Data;
if (callResult) {
if (callResult.Data) {
const [offset, length] = callResult.Data;
contractCallResults[i] = returnData.slice(Number(offset), Number(offset + length));
} else {
contractCallResults[i] = new NumberCoder('u64').encode(callResult.Some.Value);
contractCallResults[i] = new NumberCoder('u64').encode(callResult.Value);
}
}
});
Expand Down