Skip to content

Commit

Permalink
feat: cairo enums in response parser
Browse files Browse the repository at this point in the history
  • Loading branch information
PhilippeR26 committed Jul 18, 2023
1 parent 66b3282 commit 55b7408
Show file tree
Hide file tree
Showing 11 changed files with 25,371 additions and 6,820 deletions.
19,483 changes: 19,482 additions & 1 deletion __mocks__/cairo/helloCairo2/compiled.casm

Large diffs are not rendered by default.

12,455 changes: 5,644 additions & 6,811 deletions __mocks__/cairo/helloCairo2/compiled.json

Large diffs are not rendered by default.

35 changes: 35 additions & 0 deletions __tests__/cairo1v2.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {
Account,
BigNumberish,
CairoCustomEnum,
CairoOption,
CallData,
Calldata,
CompiledSierra,
Expand Down Expand Up @@ -328,6 +330,39 @@ describe('Cairo 1 Devnet', () => {
});
});

test('CairoEnums', async () => {
type Order = {
p1: BigNumberish;
p2: BigNumberish;
};
// return a Cairo Custom Enum
const myCairoEnum: CairoCustomEnum = await cairo1Contract.my_enum_output(50);
expect(myCairoEnum.unwrap()).toEqual(3n);
expect(myCairoEnum.activeVariant()).toEqual('Error');

const myCairoEnum2: CairoCustomEnum = await cairo1Contract.my_enum_output(100);
expect(myCairoEnum2.unwrap()).toEqual(BigInt(shortString.encodeShortString('attention:100')));
expect(myCairoEnum2.activeVariant()).toEqual('Warning');

const myCairoEnum3: CairoCustomEnum = await cairo1Contract.my_enum_output(150);
const res: Order = myCairoEnum3.unwrap();
expect(res).toEqual({ p1: 1n, p2: 150n });
expect(myCairoEnum3.activeVariant()).toEqual('Response');

// return a Cairo Option
const myCairoOption: CairoOption<Order> = await cairo1Contract.option_order_output(50);
expect(myCairoOption.unwrap()).toEqual(undefined);
expect(myCairoOption.isNone()).toEqual(true);
expect(myCairoOption.isSome()).toEqual(false);

const myCairoOption2: CairoOption<Order> = await cairo1Contract.option_order_output(150);
expect(myCairoOption2.unwrap()).toEqual({ p1: 18n, p2: 150n });
expect(myCairoOption2.isNone()).toEqual(false);
expect(myCairoOption2.isSome()).toEqual(true);

// TODO : Cairo Result will be tested when Cairo >=v2.1.0 will be implemented in Starknet devnet.
});

test('myCallData.compile for Cairo 1', async () => {
const myFalseUint256 = { high: 1, low: 23456 }; // wrong order
type Order2 = {
Expand Down
15 changes: 13 additions & 2 deletions src/utils/calldata/enum/CairoCustomEnum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class CairoCustomEnum {
readonly variant: CairoEnumRaw;

constructor(enumContent: CairoEnumRaw) {
// TODO : add checks of validity of enumContent
// TODO for request Parser : add checks of validity of enumContent
this.variant = enumContent;
}

Expand All @@ -39,5 +39,16 @@ export class CairoCustomEnum {
return activeVariant[1];
}

// TODO : add function 'activeVariant' -> string
/**
*
* @returns the name of the valid variant of a Cairo custom Enum.
*/
public activeVariant(): string {
const variants = Object.entries(this.variant);
const activeVariant = variants.find((item) => typeof item[1] !== 'undefined');
if (typeof activeVariant === 'undefined') {
return '';
}
return activeVariant[0];
}
}
5 changes: 4 additions & 1 deletion src/utils/calldata/enum/CairoOption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@ export class CairoOption<T> {
);
}
this.Some = someContent;
this.None = undefined;
} else {
this.Some = undefined;
this.None = true;
}
this.None = true;
}

/**
Expand Down
5 changes: 4 additions & 1 deletion src/utils/calldata/enum/CairoResult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ export class CairoResult<T, U> {
constructor(variant: CairoResultVariant, resultContent: T | U) {
if (variant === CairoResultVariant.Ok) {
this.Ok = resultContent as T;
this.Err = undefined;
} else {
this.Ok = undefined;
this.Err = resultContent as U;
}
this.Err = resultContent as U;
}

/**
Expand Down
4 changes: 3 additions & 1 deletion src/utils/calldata/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ export class CallData {
* @returns AbiEnums - enums from abi
*/
static getAbiEnum(abi: Abi): AbiEnums {
return abi
const fullEnumList = abi
.filter((abiEntry) => abiEntry.type === 'enum')
.reduce(
(acc, abiEntry) => ({
Expand All @@ -234,6 +234,8 @@ export class CallData {
}),
{}
);
delete fullEnumList['core::bool'];
return fullEnumList;
}

/**
Expand Down
28 changes: 27 additions & 1 deletion src/utils/calldata/responseParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,14 @@ import {
isTypeTuple,
isTypeUint256,
} from './cairo';
import { CairoCustomEnum, CairoEnumRaw } from './enum';
import {
CairoCustomEnum,
CairoEnumRaw,
CairoOption,
CairoOptionVariant,
CairoResult,
CairoResultVariant,
} from './enum';
import extractTupleMemberTypes from './tuple';

/**
Expand Down Expand Up @@ -58,6 +65,9 @@ function parseResponseValue(
structs: AbiStructs,
enums: AbiEnums
): BigNumberish | ParsedStruct | boolean | any[] | CairoEnum {
if (element.type === '()') {
return {};
}
// type uint256 struct (c1v2)
if (isTypeUint256(element.type)) {
const low = responseIterator.next().value;
Expand Down Expand Up @@ -89,6 +99,22 @@ function parseResponseValue(
acc[variant.name] = undefined;
return acc;
}, {} as CairoEnumRaw);
// Option
if (element.type.startsWith('core::option::Option')) {
const content = variantNum === CairoOptionVariant.Some ? rawEnum.Some : undefined;
return new CairoOption<Object>(variantNum, content);
}
// Result
if (element.type.startsWith('core::result::Result')) {
let content: Object;
if (variantNum === CairoResultVariant.Ok) {
content = rawEnum.Ok;
} else {
content = rawEnum.Err;
}
return new CairoResult<Object, Object>(variantNum, content);
}
// Cairo custom Enum
const customEnum = new CairoCustomEnum(rawEnum);
return customEnum;
}
Expand Down
157 changes: 157 additions & 0 deletions www/docs/guides/cairo_enum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
---
sidebar_position: 16
---

# Cairo Enums

## Cairo Enums usage

Cairo-lang v0.12.0 (including Cairo v2.0.0) introduces a new type of data that can be exchanged with Starknet : the Enums. Nothing related to Typescript Enums, the Cairo Enums are identical to Rust Enums.
More information in the Starknet book [here](https://book.starknet.io/chapter_2/enums.html).
In the following paragraphers, you will see how to receive Enums from the Starknet network.

To send an Enum to Starknet, Starknet.js can only be used this way (for the moment) :

```typescript
const res = await myTestContract.call("test", [0, 10, 11], { parseRequest: false, parseResponse: false });
```

The first number is the variant number, then the data of this variant.
It will be improved in a near future.

## Cairo Option

The `Option` Enum is a core enum, and has 2 variants (`Some` and `None`). Only the `some` variant can contain data.
An example of Cairo code that uses the Option enum :

```rust
fn test(self: @ContractState, val1: u16) -> Option<Order> {
if val1 < 100 {
return Option::None(());
}
Option::Some(Order { p1: 18, p2: val1 })
}
```

In your code, the Starknet.js response will be an instance of the CairoOption class :

```typescript
import { CairoOption } from "starknet";
type Order = {
p1: BigNumberish,
p2: BigNumberish,
}
const res: CairoOption<Order> = await myTestContract.test(50);
const res2: CairoOption<Order> = await myTestContract.test(150);
```

In `CairoOption<T>`, T is the type of the data related to the `Some` variant.
The `CairoOption` class has "Cairo like" methods :

```typescript
const a = res.isSome(); // false
const a2 = res2.isSome(); // true
const b = res.isNone(); // true
const b2 = res2.isNone(); // false
const c = res.unwrap(); // undefined
const c2: Order = res2.unwrap(); // { p1: 18n, p2: 150n }
```

## Cairo Result

Cairo v2.0.0 introduces an other core Enum : `Result`.
This Enum has 2 variants (`Ok` and `Err`) and both variants can contain data.
An example of Cairo code that uses the Result enum :

```rust
fn test(self: @ContractState, val1: u16) -> Result<u16, u16> {
if val1 < 100 {
return Result::Err(14);
}
Result::Ok(val1)
}
```

In your code, the Starknet.js response will be an instance of the CairoResult class :

```typescript
import { CairoResult } from "starknet";

const res:CairoResult<bigint, bigint> = await myTestContract.test(90);
const res2 = (await myTestContract.call("test",[110])) as CairoResult<bigint, bigint>;
```

In `CairoResult<T, U>`, T is the type of the data related to the `Ok` variant, and U is the type of the data related to the `Err` variant.
The `CairoResult` class has "Cairo like" methods :

```typescript
const a = res.isOk(); // false
const a2 = res2.isOk(); // true
const b = res.isErr(); // true
const b2 = res2.isErr(); // false
const c = res.unwrap(); // 14n
const c2 = res2.unwrap(); // 110n
```

## Cairo custom Enum

In Cairo v2.0.0, you can also create your own customized Enum.
An example of Cairo code that uses the Result enum :

```rust
#[derive(Drop, Serde, Append)]
enum MyEnum {
Response: Order,
Warning: felt252,
Error: (u16,u16),
Critical: Array<u32>,
}
fn test(self: @ContractState, val1: u16) -> MyEnum {
if val1 < 100 {
return MyEnum::Error((3,4));
}
if val1 == 100 {
return MyEnum::Warning('attention:100');
}
if val1 < 150 {
let mut arr=ArrayTrait::new();
arr.append(5);
arr.append(6);
return MyEnum::Critical(arr);
}
MyEnum::Response(Order { p1: 1, p2: val1 })
}
```
This example Enum has 4 variants (`Response`, `Warning`, `Error` and `Critical`) and both variants can contain data.
In your code, the Starknet.js response will be an instance of the CairoCustomEnum class :
```typescript
import { CairoCustomEnum } from "starknet";
const res: CairoCustomEnum = await myTestContract.test(10);
const res2: CairoCustomEnum = await myTestContract.test(100);
const res3: CairoCustomEnum = await myTestContract.test(120);
const res4: CairoCustomEnum = await myTestContract.test(190);
```
The `CairoCustomEnum` class has "Cairo like" methods :
```typescript
const a = res.activeVariant(); // "Error"
const a2 = res2.activeVariant(); // "Warning"
const a3 = res3.activeVariant(); // "Critical"
const a4 = res4.activeVariant(); // "Response"
const c = res.unwrap(); // {"0": 3n, "1": 4n}
const c2: bigint = res2.unwrap(); // 7721172739414537047772488609840n
const c3: bigint[] = res3.unwrap(); // [5n, 6n]
const c4: Order = res4.unwrap(); // { p1: 1n, p2: 190n }
```
> In a `CairoCustomEnum` instance, you can also have a direct access to the content of a variant :
```typescript
const d: Order = res4.variant.Response // { p1: 1n, p2: 190n }
const e = res4.variant["Critical"] // undefined
```
2 changes: 1 addition & 1 deletion www/docs/guides/cra.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 16
sidebar_position: 17
---

# Usage with Create React App
Expand Down
2 changes: 1 addition & 1 deletion www/docs/guides/migrate.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 17
sidebar_position: 18
---

# Migrate from v4 to v5
Expand Down

0 comments on commit 55b7408

Please sign in to comment.