Skip to content

Commit

Permalink
feat: cairo enum in request parser
Browse files Browse the repository at this point in the history
  • Loading branch information
PhilippeR26 committed Jul 31, 2023
1 parent a936a11 commit ad44481
Show file tree
Hide file tree
Showing 15 changed files with 6,587 additions and 5,599 deletions.
1,215 changes: 812 additions & 403 deletions __mocks__/cairo/helloCairo2/compiled.casm

Large diffs are not rendered by default.

10,232 changes: 5,184 additions & 5,048 deletions __mocks__/cairo/helloCairo2/compiled.json

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions __mocks__/cairo/helloCairo2/hello.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ 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;
Expand Down Expand Up @@ -351,6 +352,16 @@ 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 Down
61 changes: 61 additions & 0 deletions __tests__/cairo1v2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
BigNumberish,
CairoCustomEnum,
CairoOption,
CairoOptionVariant,
CallData,
Calldata,
CompiledSierra,
Expand Down Expand Up @@ -366,6 +367,50 @@ describe('Cairo 1 Devnet', () => {
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);
Expand All @@ -377,6 +422,22 @@ describe('Cairo 1 Devnet', () => {
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);

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

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

export type CairoEnum = CairoCustomEnum | CairoOption<Object> | CairoResult<Object, Object>;
export type CairoEnum = CairoCustomEnum | CairoOption<any> | CairoResult<any, any>;
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
2 changes: 2 additions & 0 deletions src/utils/calldata/cairo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ 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
14 changes: 13 additions & 1 deletion src/utils/calldata/enum/CairoCustomEnum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,20 @@ export class CairoCustomEnum {
*/
readonly variant: CairoEnumRaw;

/**
* @param enumContent an object with the variants as keys and the content as value. Only one content shall be defined.
*/
constructor(enumContent: CairoEnumRaw) {
// TODO for request Parser : add checks of validity of enumContent
const variantsList = Object.values(enumContent);
if (variantsList.length === 0) {
throw new Error('This Enum must have a least 1 variant');
}
const nbActiveVariants = variantsList.filter(
(content) => typeof content !== 'undefined'
).length;
if (nbActiveVariants !== 1) {
throw new Error('This Enum must have exactly one active variant');
}
this.variant = enumContent;
}

Expand Down
3 changes: 3 additions & 0 deletions src/utils/calldata/enum/CairoOption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ export class CairoOption<T> {
readonly None?: boolean;

constructor(variant: CairoOptionVariant, someContent?: T) {
if (!(variant in CairoOptionVariant)) {
throw new Error('Wrong variant : should be CairoOptionVariant.Some or .None.');
}
if (variant === CairoOptionVariant.Some) {
if (typeof someContent === 'undefined') {
throw new Error(
Expand Down
3 changes: 3 additions & 0 deletions src/utils/calldata/enum/CairoResult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ export class CairoResult<T, U> {
readonly Err?: U;

constructor(variant: CairoResultVariant, resultContent: T | U) {
if (!(variant in CairoResultVariant)) {
throw new Error('Wrong variant : should be CairoResultVariant.Ok or .Err.');
}
if (variant === CairoResultVariant.Ok) {
this.Ok = resultContent as T;
this.Err = undefined;
Expand Down
70 changes: 61 additions & 9 deletions src/utils/calldata/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ import { isBigInt, toHex } from '../num';
import { getSelectorFromName } from '../selector';
import { isLongText, splitLongString } from '../shortString';
import { felt, isLen } from './cairo';
import {
CairoCustomEnum,
CairoOption,
CairoOptionVariant,
CairoResult,
CairoResultVariant,
} from './enum';
import formatter from './formatter';
import { createAbiParser, isNoConstructorValid } from './parser';
import { AbiParserInterface } from './parser/interface';
Expand Down Expand Up @@ -86,7 +93,7 @@ export class CallData {
}

// validate parameters
validateFields(abiMethod, args, this.structs);
validateFields(abiMethod, args, this.structs, this.enums);
}

/**
Expand All @@ -104,7 +111,7 @@ export class CallData {
* ```
*/
public compile(method: string, argsCalldata: RawArgs): Calldata {
const abiMethod = this.abi.find((abi) => abi.name === method) as FunctionAbi;
const abiMethod = this.abi.find((abiFunction) => abiFunction.name === method) as FunctionAbi;

if (isNoConstructorValid(method, argsCalldata, abiMethod)) {
return [];
Expand All @@ -115,17 +122,25 @@ export class CallData {
args = argsCalldata;
} else {
// order the object
const orderedObject = orderPropsByAbi(argsCalldata, abiMethod.inputs, this.structs);
const orderedObject = orderPropsByAbi(
argsCalldata,
abiMethod.inputs,
this.structs,
this.enums
);
// console.log('ordered =', orderedObject);
args = Object.values(orderedObject);
// // validate array elements to abi
validateFields(abiMethod, args, this.structs);
validateFields(abiMethod, args, this.structs, this.enums);
}

const argsIterator = args[Symbol.iterator]();

const callArray = abiMethod.inputs.reduce(
(acc, input) =>
isLen(input.name) ? acc : acc.concat(parseCalldataField(argsIterator, input, this.structs)),
isLen(input.name)
? acc
: acc.concat(parseCalldataField(argsIterator, input, this.structs, this.enums)),
[] as Calldata
);

Expand Down Expand Up @@ -153,12 +168,49 @@ export class CallData {
if (k === 'entrypoint') value = getSelectorFromName(value);
const kk = Array.isArray(oe) && k === '0' ? '$$len' : k;
if (isBigInt(value)) return [[`${prefix}${kk}`, felt(value)]];
return Object(value) === value
? getEntries(value, `${prefix}${kk}.`)
: [[`${prefix}${kk}`, felt(value)]];
if (Object(value) === value) {
const methodsKeys = Object.getOwnPropertyNames(Object.getPrototypeOf(value));
const keys = [...Object.getOwnPropertyNames(value), ...methodsKeys];
if (keys.includes('isSome') && keys.includes('isNone')) {
// Option
const myOption = value as CairoOption<any>;
const variantNb = myOption.isSome()
? CairoOptionVariant.Some
: CairoOptionVariant.None;
if (myOption.isSome())
return getEntries({ 0: variantNb, 1: myOption.unwrap() }, `${prefix}${kk}.`);
return [[`${prefix}${kk}`, felt(variantNb)]];
}
if (keys.includes('isOk') && keys.includes('isErr')) {
// Result
const myResult = value as CairoResult<any, any>;
const variantNb = myResult.isOk() ? CairoResultVariant.Ok : CairoResultVariant.Err;
return getEntries({ 0: variantNb, 1: myResult.unwrap() }, `${prefix}${kk}.`);
}
if (keys.includes('variant') && keys.includes('activeVariant')) {
// CustomEnum
const myEnum = value as CairoCustomEnum;
const activeVariant: string = myEnum.activeVariant();
const listVariants = Object.keys(myEnum.variant);
const activeVariantNb = listVariants.findIndex(
(variant: any) => variant === activeVariant
);
if (
typeof myEnum.unwrap() === 'object' &&
Object.keys(myEnum.unwrap()).length === 0 // empty object : {}
) {
return [[`${prefix}${kk}`, felt(activeVariantNb)]];
}
return getEntries({ 0: activeVariantNb, 1: myEnum.unwrap() }, `${prefix}${kk}.`);
}
// normal object
return getEntries(value, `${prefix}${kk}.`);
}
return [[`${prefix}${kk}`, felt(value)]];
});
};
return Object.fromEntries(getEntries(obj));
const result = Object.fromEntries(getEntries(obj));
return result;
};

let callTreeArray;
Expand Down
Loading

0 comments on commit ad44481

Please sign in to comment.