Skip to content

Commit

Permalink
Merge pull request #690 from PhilippeR26/cairo-enum-response
Browse files Browse the repository at this point in the history
feat: cairo enums in request and response parsers
  • Loading branch information
tabaktoni authored Aug 10, 2023
2 parents 9a3fa41 + 00f8073 commit 9b77cf6
Show file tree
Hide file tree
Showing 25 changed files with 15,228 additions and 11,405 deletions.
10,730 changes: 7,771 additions & 2,959 deletions __mocks__/cairo/helloCairo2/compiled.casm

Large diffs are not rendered by default.

14,624 changes: 6,329 additions & 8,295 deletions __mocks__/cairo/helloCairo2/compiled.json

Large diffs are not rendered by default.

48 changes: 43 additions & 5 deletions __mocks__/cairo/helloCairo2/hello.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use serde::Serde;
// bet part
use starknet::ContractAddress;
use starknet::get_caller_address;
use starknet::StorageAccess;
use starknet::Store;
use starknet::storage_access;
use starknet::StorageBaseAddress;
use starknet::SyscallResult;
Expand All @@ -27,13 +27,13 @@ struct Foo {
}

// Complex Structs
#[derive(Copy, Drop, Serde, storage_access::StorageAccess)]
#[derive(Copy, Drop, Serde, starknet::Store)]
struct UserData {
address: ContractAddress,
is_claimed: bool,
}

#[derive(Copy, Drop, Serde, storage_access::StorageAccess)]
#[derive(Copy, Drop, Serde, starknet::Store)]
struct Bet {
name: felt252,
description: felt252,
Expand Down Expand Up @@ -135,9 +135,12 @@ trait IHelloStarknet<TContractState> {
// used for changes to redeclare contract
fn array2ddd_felt(self: @TContractState, testdd: Array<Array<felt252>>) -> felt252;
fn my_enum_output(self: @TContractState, val1: u16) -> MyEnum;
fn my_enum_input(self: @TContractState, customEnum: MyEnum) -> u16;
fn option_u8_output(self: @TContractState, val1: u8) -> Option<u8>;
fn option_order_output(self: @TContractState, val1: u16) -> Option<Order>;
fn option_order_input(self: @TContractState, inp: Option<Order>) -> u16;
fn enum_result_output(self: @TContractState, val1: u16) -> Result<Order, u16>;
fn enum_result_input(self: @TContractState, inp: Result<Order, u16>) -> u16;
}

// MAIN APP
Expand All @@ -154,7 +157,7 @@ mod HelloStarknet {
// bet part
use starknet::ContractAddress;
use starknet::get_caller_address;
use starknet::StorageAccess;
use starknet::Store;
use starknet::storage_access;
use starknet::StorageBaseAddress;
use starknet::SyscallResult;
Expand Down Expand Up @@ -190,7 +193,7 @@ mod HelloStarknet {
EventRegular: EventRegular,
EventNested: EventNested,
}

#[derive(Drop, Serde)]
struct SimpleStruct {
first: u8,
Expand Down Expand Up @@ -443,6 +446,22 @@ mod HelloStarknet {
}
MyEnum::Response(Order { p1: 1, p2: val1 })
}

// MyEnum as input
fn my_enum_input(self: @ContractState, customEnum: MyEnum) -> u16 {
match customEnum {
MyEnum::Response(my_order) => {
return my_order.p2;
},
MyEnum::Warning(val) => {
return 0x13_u16;
},
MyEnum::Error(a) => {
return a;
}
}
}

// return Option<litteral>
fn option_u8_output(self: @ContractState, val1: u8) -> Option<u8> {
if val1 < 100 {
Expand All @@ -468,5 +487,24 @@ mod HelloStarknet {
}
}
}

// return Result<Order>
fn enum_result_output(self: @ContractState, val1: u16) -> Result<Order, u16> {
if val1 < 100 {
return Result::Err(14);
}
Result::Ok(Order { p2: val1, p1: 8 })
}
// use as input Result<Order>
fn enum_result_input(self: @ContractState, inp: Result<Order, u16>) -> u16 {
match inp {
Result::Ok(x) => {
return x.p2;
},
Result::Err(y) => {
return y;
}
}
}
}
}
125 changes: 125 additions & 0 deletions __tests__/cairo1v2.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import {
Account,
BigNumberish,
CairoCustomEnum,
CairoOption,
CairoOptionVariant,
CairoResult,
CairoResultVariant,
CallData,
Calldata,
CompiledSierra,
Expand Down Expand Up @@ -346,6 +351,126 @@ 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');

// Send a Cairo Custom Enum
const res2 = (await cairo1Contract.call('my_enum_input', [
new CairoCustomEnum({ Error: 100 }),
])) as bigint;
const myOrder: Order = { p1: 100, p2: 200 };
const res3 = (await cairo1Contract.my_enum_input(
new CairoCustomEnum({ Response: myOrder })
)) as bigint;
expect(res2).toEqual(100n);
expect(res3).toEqual(200n);

const comp2 = CallData.compile([
new CairoCustomEnum({
Response: undefined,
Warning: undefined,
Error: 100,
}),
]);
const res2a = (await cairo1Contract.call('my_enum_input', comp2)) as bigint;
const comp3 = CallData.compile([
new CairoCustomEnum({
Response: myOrder,
Warning: undefined,
Error: undefined,
}),
]);
const res3a = (await cairo1Contract.my_enum_input(comp3)) as bigint;
expect(res2a).toEqual(100n);
expect(res3a).toEqual(200n);

const comp2b = cairo1Contract.populate('my_enum_input', {
customEnum: new CairoCustomEnum({ Error: 100 }),
});
const res2b = (await cairo1Contract.call(
'my_enum_input',
comp2b.calldata as Calldata
)) as bigint;
const comp3b = cairo1Contract.populate('my_enum_input', {
customEnum: new CairoCustomEnum({ Response: myOrder }),
});
const res3b = (await cairo1Contract.my_enum_input(comp3b.calldata)) as bigint;
expect(res2b).toEqual(100n);
expect(res3b).toEqual(200n);

// 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);

// send a Cairo Option
const cairoOption1 = new CairoOption<Order>(CairoOptionVariant.None);
const res4 = (await cairo1Contract.call('option_order_input', [cairoOption1])) as bigint;
const comp4a = CallData.compile([cairoOption1]);
const res4a = (await cairo1Contract.call('option_order_input', comp4a)) as bigint;
const res5 = (await cairo1Contract.option_order_input(
new CairoOption<Order>(CairoOptionVariant.Some, myOrder)
)) as bigint;
const res5a = (await cairo1Contract.option_order_input(
CallData.compile([new CairoOption<Order>(CairoOptionVariant.Some, myOrder)])
)) as bigint;
expect(res4).toEqual(17n);
expect(res4a).toEqual(17n);
expect(res5).toEqual(200n);
expect(res5a).toEqual(200n);

// return a Cairo Result
const myCairoResult: CairoResult<Order, BigNumberish> =
await cairo1Contract.enum_result_output(50);
expect(myCairoResult.unwrap()).toEqual(14n);
expect(myCairoResult.isErr()).toEqual(true);
expect(myCairoResult.isOk()).toEqual(false);

const myCairoResult2: CairoResult<Order, BigNumberish> =
await cairo1Contract.enum_result_output(150);
expect(myCairoResult2.unwrap()).toEqual({ p1: 8n, p2: 150n });
expect(myCairoResult2.isErr()).toEqual(false);
expect(myCairoResult2.isOk()).toEqual(true);

// send a Cairo Result
const cairoResult1 = new CairoResult<Order, BigNumberish>(CairoResultVariant.Err, 18n);
const res6 = (await cairo1Contract.call('enum_result_input', [cairoResult1])) as bigint;
const comp6a = CallData.compile([cairoResult1]);
const res6a = (await cairo1Contract.call('enum_result_input', comp6a)) as bigint;
const res7 = (await cairo1Contract.enum_result_input(
new CairoResult<Order, BigNumberish>(CairoResultVariant.Ok, myOrder)
)) as bigint;
const res7a = (await cairo1Contract.enum_result_input(
CallData.compile([new CairoResult<Order, BigNumberish>(CairoResultVariant.Ok, myOrder)])
)) as bigint;
expect(res6).toEqual(18n);
expect(res6a).toEqual(18n);
expect(res7).toEqual(200n);
expect(res7a).toEqual(200n);
});

test('myCallData.compile for Cairo 1', async () => {
const myFalseUint256 = { high: 1, low: 23456 }; // wrong order
type Order2 = {
Expand Down
3 changes: 2 additions & 1 deletion src/contract/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,8 @@ export class Contract implements ContractInterface {
[]
) || [],
this.events,
this.structs
this.structs,
CallData.getAbiEnum(this.abi)
);
}

Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export * as selector from './utils/selector';
export * from './utils/address';
export * from './utils/url';
export * from './utils/calldata';
export * from './utils/calldata/enum';
export * from './utils/contract';
export * from './utils/events';

Expand Down
3 changes: 3 additions & 0 deletions src/types/cairoEnum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { CairoCustomEnum, CairoOption, CairoResult } from '../utils/calldata/enum';

export type CairoEnum = CairoCustomEnum | CairoOption<any> | CairoResult<any, any>;
5 changes: 4 additions & 1 deletion src/types/contract.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CairoEnum } from './cairoEnum';
import {
BigNumberish,
BlockIdentifier,
Expand All @@ -9,14 +10,16 @@ import {

export type AsyncContractFunction<T = any> = (...args: ArgsOrCalldataWithOptions) => Promise<T>;
export type ContractFunction = (...args: ArgsOrCalldataWithOptions) => any;

export type Result =
| {
[key: string]: any;
}
| Result[]
| bigint
| string
| boolean;
| boolean
| CairoEnum;

export type ArgsOrCalldata = RawArgsArray | [Calldata] | Calldata;
export type ArgsOrCalldataWithOptions = ArgsOrCalldata & ContractOptions;
Expand Down
1 change: 1 addition & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export * from './lib';
export * from './provider';
export * from './signer';
export * from './typedData';
export * from './cairoEnum';

export * as RPC from './api/rpc';
export {
Expand Down
8 changes: 8 additions & 0 deletions src/types/lib/contract/abi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ export type StructAbi = {
type: 'struct';
};

export type AbiEnums = { [name: string]: EnumAbi };
export type EnumAbi = {
variants: (AbiEntry & { offset: number })[];
name: string;
size: number;
type: 'enum';
};

export type AbiEvents = { [hash: string]: EventAbi };

export type EventAbi = Cairo1Event | LegacyEvent;
Expand Down
3 changes: 2 additions & 1 deletion src/types/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { StarknetChainId } from '../../constants';
import { weierstrass } from '../../utils/ec';
import { CairoEnum } from '../cairoEnum';
import { CompiledContract, CompiledSierraCasm, ContractClass } from './contract';

export type WeierstrassSignatureType = weierstrass.SignatureType;
Expand Down Expand Up @@ -47,7 +48,7 @@ export type RawArgsObject = {

export type RawArgsArray = Array<MultiType | MultiType[] | RawArgs>;

export type MultiType = BigNumberish | Uint256 | object | boolean;
export type MultiType = BigNumberish | Uint256 | object | boolean | CairoEnum;

export type UniversalDeployerContractPayload = {
classHash: BigNumberish;
Expand Down
5 changes: 4 additions & 1 deletion src/utils/calldata/cairo.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Abi, AbiStructs, BigNumberish, Uint, Uint256 } from '../../types';
import { Abi, AbiEnums, AbiStructs, BigNumberish, Uint, Uint256 } from '../../types';
import { isBigInt, isHex, isStringWholeNumber } from '../num';
import { encodeShortString, isShortString, isText } from '../shortString';
import { UINT_128_MAX, isUint256 } from '../uint256';
Expand All @@ -10,6 +10,9 @@ export const isTypeArray = (type: string) =>
export const isTypeTuple = (type: string) => /^\(.*\)$/i.test(type);
export const isTypeNamedTuple = (type: string) => /\(.*\)/i.test(type) && type.includes(':');
export const isTypeStruct = (type: string, structs: AbiStructs) => type in structs;
export const isTypeEnum = (type: string, enums: AbiEnums) => type in enums;
export const isTypeOption = (type: string) => type.startsWith('core::option::Option::');
export const isTypeResult = (type: string) => type.startsWith('core::result::Result::');
export const isTypeUint = (type: string) => Object.values(Uint).includes(type as Uint);
export const isTypeUint256 = (type: string) => type === 'core::integer::u256';
export const isTypeBool = (type: string) => type === 'core::bool';
Expand Down
Loading

0 comments on commit 9b77cf6

Please sign in to comment.