diff --git a/src/index.ts b/src/index.ts index b1ededfba..777140ee1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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'; diff --git a/src/types/cairoEnum.ts b/src/types/cairoEnum.ts new file mode 100644 index 000000000..7325edb39 --- /dev/null +++ b/src/types/cairoEnum.ts @@ -0,0 +1,3 @@ +import { CairoCustomEnum, CairoOption, CairoResult } from '../utils/calldata/enum'; + +export type CairoEnum = CairoCustomEnum | CairoOption | CairoResult; diff --git a/src/types/contract.ts b/src/types/contract.ts index 924ec6dcb..914d76e01 100644 --- a/src/types/contract.ts +++ b/src/types/contract.ts @@ -1,26 +1,9 @@ +import { CairoEnum } from './cairoEnum'; import { BigNumberish, BlockIdentifier, RawArgsArray, Signature } from './lib'; export type AsyncContractFunction = (...args: ArgsOrCalldataWithOptions) => Promise; export type ContractFunction = (...args: ArgsOrCalldataWithOptions) => any; -export interface CairoCustomEnumInterface { - [key: string]: any; - unwrap: Function; -} -export interface CairoOptionInterface { - [key: string]: any; - unwrap: Function; - isSome: Function; - isNone: Function; -} -export interface CairoResultInterface { - [key: string]: any; - unwrap: Function; - isOK: Function; - isErr: Function; -} -export type CairoEnum = CairoCustomEnumInterface | CairoOptionInterface | CairoResultInterface; - export type Result = | { [key: string]: any; diff --git a/src/types/index.ts b/src/types/index.ts index ae41cbca2..5c17a17cc 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -7,3 +7,4 @@ export * from './typedData'; export * from './api/sequencer'; export * from './api/rpc'; export * from './calldata'; +export * from './cairoEnum'; diff --git a/src/utils/calldata/enum/CairoCustomEnum.ts b/src/utils/calldata/enum/CairoCustomEnum.ts new file mode 100644 index 000000000..229e6c287 --- /dev/null +++ b/src/utils/calldata/enum/CairoCustomEnum.ts @@ -0,0 +1,43 @@ +export type CairoEnumRaw = { + [key: string]: any; +}; + +/** + * Class to handle Cairo custom Enum + * @param enumContent object containing the variants and its content. Example : + * {Success: 234, Warning: undefined, Error: undefined}. + * Only one variant with a value, object, array. + * @returns an instance representing a Cairo custom Enum. + * @example ```typescript + * const myCairoEnum = new CairoCustomEnum( {Success: undefined, Warning: "0x7f32ea", Error: undefined}) + * ``` + */ +export class CairoCustomEnum { + /** + * direct readonly access to variants of the Cairo Custom Enum. + * @returns a value of type any + * @example ```typescript + * const successValue = myCairoEnum.variant.Success; + */ + readonly variant: CairoEnumRaw; + + constructor(enumContent: CairoEnumRaw) { + // TODO : add checks of validity of enumContent + this.variant = enumContent; + } + + /** + * + * @returns the content of the valid variant of a Cairo custom Enum. + */ + public unwrap(): any { + const variants = Object.entries(this.variant); + const activeVariant = variants.find((item) => typeof item[1] !== 'undefined'); + if (typeof activeVariant === 'undefined') { + return undefined; + } + return activeVariant[1]; + } + + // TODO : add function 'activeVariant' -> string +} diff --git a/src/utils/calldata/enum/CairoOption.ts b/src/utils/calldata/enum/CairoOption.ts new file mode 100644 index 000000000..d88cd513f --- /dev/null +++ b/src/utils/calldata/enum/CairoOption.ts @@ -0,0 +1,59 @@ +export enum CairoOptionVariant { + Some = 0, + None = 1, +} + +/** + * Class to handle Cairo Option + * @param variant CairoOptionVariant.Some or CairoOptionVariant.None + * @param someContent value of type T. + * @returns an instance representing a Cairo Option. + * @example ```typescript + * const myOption = new CairoOption(CairoOptionVariant.Some, "0x54dda8"); + * ``` + */ +export class CairoOption { + readonly Some?: T; + + readonly None?: boolean; + + constructor(variant: CairoOptionVariant, someContent?: T) { + if (variant === CairoOptionVariant.Some) { + if (typeof someContent === 'undefined') { + throw new Error( + 'The creation of a Cairo Option with "Some" variant needs a content as input.' + ); + } + this.Some = someContent; + } + this.None = true; + } + + /** + * + * @returns the content of the valid variant of a Cairo custom Enum. + * If None, returns 'undefined'. + */ + public unwrap(): T | undefined { + if (this.None) { + return undefined; + } + return this.Some; + } + + /** + * + * @returns true if the valid variant is 'isSome'. + */ + public isSome(): boolean { + return !(typeof this.Some === 'undefined'); + } + + /** + * + * @returns true if the valid variant is 'isNone'. + */ + public isNone(): boolean { + return this.None === true; + } +} diff --git a/src/utils/calldata/enum/CairoResult.ts b/src/utils/calldata/enum/CairoResult.ts new file mode 100644 index 000000000..bf806453a --- /dev/null +++ b/src/utils/calldata/enum/CairoResult.ts @@ -0,0 +1,56 @@ +export enum CairoResultVariant { + Ok = 0, + Err = 1, +} + +/** + * Class to handle Cairo Result + * @param variant CairoResultVariant.Ok or CairoResultVariant.Err + * @param resultContent value of type T or U. + * @returns an instance representing a Cairo Result. + * @example ```typescript + * const myOption = new CairoResult(CairoResultVariant.Ok, "0x54dda8"); + * ``` + */ +export class CairoResult { + readonly Ok?: T; + + readonly Err?: U; + + constructor(variant: CairoResultVariant, resultContent: T | U) { + if (variant === CairoResultVariant.Ok) { + this.Ok = resultContent as T; + } + this.Err = resultContent as U; + } + + /** + * + * @returns the content of the valid variant of a Cairo Result. + */ + public unwrap(): T | U { + if (typeof this.Ok !== 'undefined') { + return this.Ok; + } + if (typeof this.Err !== 'undefined') { + return this.Err; + } + throw new Error('Both Result.Ok and .Err are undefined. Not authorized.'); + } + + /** + * + * @returns true if the valid variant is 'Ok'. + */ + public isOk(): boolean { + return !(typeof this.Ok === 'undefined'); + } + + /** + * + * @returns true if the valid variant is 'isErr'. + */ + public isErr(): boolean { + return !(typeof this.Err === 'undefined'); + } +} diff --git a/src/utils/calldata/enum/index.ts b/src/utils/calldata/enum/index.ts new file mode 100644 index 000000000..a9132c871 --- /dev/null +++ b/src/utils/calldata/enum/index.ts @@ -0,0 +1,3 @@ +export * from './CairoCustomEnum'; +export * from './CairoOption'; +export * from './CairoResult'; diff --git a/src/utils/calldata/responseParser.ts b/src/utils/calldata/responseParser.ts index 9d0ffbb19..02dbc2a13 100644 --- a/src/utils/calldata/responseParser.ts +++ b/src/utils/calldata/responseParser.ts @@ -5,7 +5,7 @@ import { AbiStructs, Args, BigNumberish, - CairoCustomEnum, + CairoEnum, ParsedStruct, } from '../../types'; import { uint256ToBN } from '../uint256'; @@ -19,6 +19,7 @@ import { isTypeTuple, isTypeUint256, } from './cairo'; +import { CairoCustomEnum, CairoEnumRaw } from './enum'; import extractTupleMemberTypes from './tuple'; /** @@ -56,7 +57,7 @@ function parseResponseValue( element: { name: string; type: string }, structs: AbiStructs, enums: AbiEnums -): BigNumberish | ParsedStruct | boolean | any[] { +): BigNumberish | ParsedStruct | boolean | any[] | CairoEnum { // type uint256 struct (c1v2) if (isTypeUint256(element.type)) { const low = responseIterator.next().value; @@ -72,9 +73,25 @@ function parseResponseValue( }, {} as any); } - // type Enum - // if (element.type in enums && enums[element.type]) { - // } + // type Enum (only CustomEnum) + if (element.type in enums && enums[element.type]) { + const variantNum: number = Number(responseIterator.next().value); // get variant number + const rawEnum = enums[element.type].variants.reduce((acc, variant, num) => { + if (num === variantNum) { + acc[variant.name] = parseResponseValue( + responseIterator, + { name: '', type: variant.type }, + structs, + enums + ); + return acc; + } + acc[variant.name] = undefined; + return acc; + }, {} as CairoEnumRaw); + const customEnum = new CairoCustomEnum(rawEnum); + return customEnum; + } // type tuple if (isTypeTuple(element.type)) { @@ -91,7 +108,7 @@ function parseResponseValue( // type c1 array if (isTypeArray(element.type)) { // eslint-disable-next-line no-case-declarations - const parsedDataArr: (BigNumberish | ParsedStruct | boolean | any[])[] = []; + const parsedDataArr: (BigNumberish | ParsedStruct | boolean | any[] | CairoEnum)[] = []; const el = { name: '', type: getArrayType(element.type) }; const len = BigInt(responseIterator.next().value); // get length while (parsedDataArr.length < len) { @@ -132,22 +149,7 @@ export default function responseParser( return parseResponseValue(responseIterator, output, structs, enums); case isTypeEnum(type, enums): - const variantNum: number = Number(responseIterator.next().value); // get variant number - const myEnum = enums[output.type].variants.reduce((acc, variant, num) => { - if (num === variantNum) { - acc[variant.name] = parseResponseValue( - responseIterator, - { name: '0', type: variant.type }, - structs, - enums - ); - return acc; - } - acc[variant.name] = undefined; - return acc; - }, {} as Record); - const customEnum: CairoCustomEnum = { ...myEnum, ...unw }; - return customEnum; + return parseResponseValue(responseIterator, output, structs, enums); case isTypeArray(type): // C1 Array @@ -156,7 +158,7 @@ export default function responseParser( } // C0 Array // eslint-disable-next-line no-case-declarations - const parsedDataArr: (BigNumberish | ParsedStruct | boolean | any[])[] = []; + const parsedDataArr: (BigNumberish | ParsedStruct | boolean | any[] | CairoEnum)[] = []; if (parsedResult && parsedResult[`${name}_len`]) { const arrLen = parsedResult[`${name}_len`] as number; while (parsedDataArr.length < arrLen) {