diff --git a/packages/nextjs/app/debug/_components/contract/Array.tsx b/packages/nextjs/app/debug/_components/contract/Array.tsx index 43a5d15f..76084c80 100644 --- a/packages/nextjs/app/debug/_components/contract/Array.tsx +++ b/packages/nextjs/app/debug/_components/contract/Array.tsx @@ -18,6 +18,7 @@ type ArrayProps = { setParentForm: (form: Record) => void; parentStateObjectKey: string; setFormErrorMessage: Dispatch>; + isDisabled?: boolean; }; export const ArrayInput = ({ @@ -27,6 +28,7 @@ export const ArrayInput = ({ parentStateObjectKey, abiParameter, setFormErrorMessage, + isDisabled, }: ArrayProps) => { // array in object representation const [inputArr, setInputArr] = useState({}); @@ -63,6 +65,7 @@ export const ArrayInput = ({ diff --git a/packages/nextjs/app/debug/_components/contract/ContractInput.tsx b/packages/nextjs/app/debug/_components/contract/ContractInput.tsx index 4fa53bdf..846935b7 100644 --- a/packages/nextjs/app/debug/_components/contract/ContractInput.tsx +++ b/packages/nextjs/app/debug/_components/contract/ContractInput.tsx @@ -13,6 +13,8 @@ import { isCairoArray, isCairoBigInt, isCairoInt, + isCairoOption, + isCairoResult, isCairoTuple, isCairoType, isCairoU256, @@ -28,6 +30,7 @@ type ContractInputProps = { stateObjectKey: string; paramType: AbiParameter; setFormErrorMessage: Dispatch>; + isDisabled?: boolean; }; export const ContractInput = ({ @@ -37,6 +40,7 @@ export const ContractInput = ({ stateObjectKey, paramType, setFormErrorMessage, + isDisabled, }: ContractInputProps) => { const inputProps = { name: stateObjectKey, @@ -62,13 +66,14 @@ export const ContractInput = ({ parentForm={form} setParentForm={setForm} setFormErrorMessage={setFormErrorMessage} + isDisabled={isDisabled} /> ); } // we prio tuples here to avoid wrong input else if (isCairoTuple(paramType.type)) { - return ; + return ; } else if ( isCairoInt(paramType.type) || isCairoBigInt(paramType.type) || @@ -78,6 +83,7 @@ export const ContractInput = ({ setFormErrorMessage((prev) => { if (!!errMessage) @@ -87,8 +93,12 @@ export const ContractInput = ({ } /> ); - } else if (isCairoType(paramType.type)) { - return ; + } else if ( + isCairoType(paramType.type) && + !isCairoResult(paramType.type) && + !isCairoOption(paramType.type) + ) { + return ; } else { return ( member.name === paramType.type, )} + isDisabled={isDisabled} /> ); } diff --git a/packages/nextjs/app/debug/_components/contract/Struct.tsx b/packages/nextjs/app/debug/_components/contract/Struct.tsx index b6c9ac9c..7f493ffb 100644 --- a/packages/nextjs/app/debug/_components/contract/Struct.tsx +++ b/packages/nextjs/app/debug/_components/contract/Struct.tsx @@ -13,6 +13,7 @@ type StructProps = { parentStateObjectKey: string; abiMember?: AbiStruct | AbiEnum; setFormErrorMessage: Dispatch>; + isDisabled?: boolean; }; export const Struct = ({ @@ -22,6 +23,7 @@ export const Struct = ({ abiMember, abi, setFormErrorMessage, + isDisabled = false, }: StructProps) => { const [form, setForm] = useState>(() => getInitialTupleFormState( @@ -29,6 +31,9 @@ export const Struct = ({ ), ); + // select enum + const [activeVariantIndex, setActiveVariantIndex] = useState(0); + // side effect to transform data before setState useEffect(() => { const values = Object.values(form); @@ -46,18 +51,9 @@ export const Struct = ({ abiMember.variants.forEach((variant, index) => { argsStruct[variant.name || `input_${index}_`] = { type: variant.type, - value: values[index], + value: index === activeVariantIndex ? values[index] : undefined, }; }); - - // check for enum validity - if (values.filter((item) => (item || "").length > 0).length > 1) { - setFormErrorMessage((prev) => - addError(prev, "enumError", "Enums can only have one active value"), - ); - } else { - setFormErrorMessage((prev) => clearError(prev, "enumError")); - } } setParentForm({ @@ -66,15 +62,19 @@ export const Struct = ({ abiMember.type === "struct" ? argsStruct : { variant: argsStruct }, }); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [abiMember, JSON.stringify(form, replacer)]); + }, [abiMember, JSON.stringify(form, replacer), activeVariantIndex]); if (!abiMember) return null; return (
-
- -
+
+ {!isDisabled && } +

{abiMember.type}

@@ -104,15 +104,28 @@ export const Struct = ({ index, ); return ( - +
+ {}} + onClick={() => { + setActiveVariantIndex(index); + }} + /> + +
); })}
diff --git a/packages/nextjs/app/debug/_components/contract/__test__/mock/mockABI.ts b/packages/nextjs/app/debug/_components/contract/__test__/mock/mockABI.ts index 6b779e12..2f670ec6 100644 --- a/packages/nextjs/app/debug/_components/contract/__test__/mock/mockABI.ts +++ b/packages/nextjs/app/debug/_components/contract/__test__/mock/mockABI.ts @@ -487,4 +487,32 @@ export const abi: any = [ }, ], }, + { + type: "enum", + name: "core::option::Option::", + variants: [ + { + name: "Some", + type: "core::integer::u256", + }, + { + name: "None", + type: "()", + }, + ], + }, + { + type: "enum", + name: "core::result::Result::", + variants: [ + { + name: "Ok", + type: "core::bool", + }, + { + name: "Err", + type: "core::integer::u64", + }, + ], + }, ]; diff --git a/packages/nextjs/app/debug/_components/contract/utilsContract.tsx b/packages/nextjs/app/debug/_components/contract/utilsContract.tsx index 160a8231..20daa26b 100644 --- a/packages/nextjs/app/debug/_components/contract/utilsContract.tsx +++ b/packages/nextjs/app/debug/_components/contract/utilsContract.tsx @@ -1,10 +1,19 @@ -import { cairo, CairoCustomEnum } from "starknet"; +import { + cairo, + CairoCustomEnum, + CairoOption, + CairoOptionVariant, + CairoResult, + CairoResultVariant, +} from "starknet"; import { isCairoArray, isCairoBigInt, isCairoBool, isCairoFelt, isCairoInt, + isCairoOption, + isCairoResult, isCairoTuple, isCairoU256, parseGenericType, @@ -101,7 +110,11 @@ export const getArgsAsStringInputFromForm = (form: Record) => { } // enum & struct - if (key.includes("contracts::")) { + if ( + key.includes("contracts::") || + isCairoResult(key) || + isCairoOption(key) + ) { type FormStructValue = { type: string; value: any; @@ -115,6 +128,56 @@ export const getArgsAsStringInputFromForm = (form: Record) => { // construct empty enums const enumObject = structObject.variant as any; const enumVariants = Object.keys(enumObject); + + // check for option + if ( + enumVariants.includes("Some") && + enumVariants.includes("None") && + isCairoOption(key) + ) { + // for some value we return with the corresponding value + + if ((enumObject.Some as FormStructValue).value !== undefined) + return new CairoOption( + CairoOptionVariant.Some, + _encodeValueFromKey( + (enumObject.Some as FormStructValue).type, + (enumObject.Some as FormStructValue).value, + ), + ); + + // set to none as default + return new CairoOption(CairoOptionVariant.None); + } + + // check for result + if ( + enumVariants.includes("Ok") && + enumVariants.includes("Err") && + isCairoResult(key) + ) { + // for some value we return with the corresponding value + if ((enumObject.Ok as FormStructValue).value !== undefined) + return new CairoResult( + CairoResultVariant.Ok, + _encodeValueFromKey( + (enumObject.Ok as FormStructValue).type, + (enumObject.Ok as FormStructValue).value, + ), + ); + else if ( + typeof (enumObject.Err as FormStructValue).value !== undefined + ) { + return new CairoResult( + CairoResultVariant.Err, + _encodeValueFromKey( + (enumObject.Err as FormStructValue).type, + (enumObject.Err as FormStructValue).value, + ), + ); + } + } + const restructuredEnum = Object.fromEntries( enumVariants.map((variant) => [ variant, @@ -127,6 +190,11 @@ export const getArgsAsStringInputFromForm = (form: Record) => { ]), ); + if (enumVariants.includes("Some") && enumVariants.includes("None")) { + console.log("options", { restructuredEnum }); + console.log("options", "we found an option"); + } + return new CairoCustomEnum(restructuredEnum); } diff --git a/packages/nextjs/app/debug/_components/contract/utilsDisplay.tsx b/packages/nextjs/app/debug/_components/contract/utilsDisplay.tsx index d1e81050..a2ced2a2 100644 --- a/packages/nextjs/app/debug/_components/contract/utilsDisplay.tsx +++ b/packages/nextjs/app/debug/_components/contract/utilsDisplay.tsx @@ -1,7 +1,12 @@ import { getChecksumAddress, Uint256 } from "starknet"; import { replacer } from "~~/utils/scaffold-stark/common"; import { AbiOutput } from "~~/utils/scaffold-stark/contract"; -import { parseGenericType } from "~~/utils/scaffold-stark/types"; +import { + isCairoArray, + isCairoOption, + isCairoResult, + parseGenericType, +} from "~~/utils/scaffold-stark/types"; import { formatEther } from "ethers"; import { Abi } from "abi-wan-kanabi"; @@ -205,6 +210,18 @@ const _decodeContractResponseItem = ( return decoded; } + // option and result are enums but we don't want to process them as enums + // possibly facing name or type that are defined as members of struct or standalone typing + // we need the fallback so that it does not crash + if ( + isCairoOption(abiType.name || "") || + isCairoOption(abiType.type || "") || + isCairoResult(abiType.name || "") || + isCairoResult(abiType.type || "") + ) { + return respItem; + } + if (abiType.type === "enum") { const variant = (respItem as any).variant; const variants = abiType.variants; @@ -277,9 +294,18 @@ export const displayType = (type: string) => { } // arrays and options - else if (type.includes("core::array") || type.includes("core::option")) { + else if (isCairoResult(type) || isCairoArray(type) || isCairoOption(type)) { const kindOfArray = type.split("::").at(2); const parsed = parseGenericType(type); + + // special handling for result since we need to pop both types + if (isCairoResult(type)) { + const type1 = parsed[0].split("::").pop(); + const type2 = parsed[1].split("::").pop(); + return `Result<${type1}, ${type2}>`; + } + + // others const arrayType = Array.isArray(parsed) ? parsed[0].split("::").pop() : `(${parsed diff --git a/packages/nextjs/components/scaffold-stark/Input/InputBase.tsx b/packages/nextjs/components/scaffold-stark/Input/InputBase.tsx index e2a264be..fb140b1b 100644 --- a/packages/nextjs/components/scaffold-stark/Input/InputBase.tsx +++ b/packages/nextjs/components/scaffold-stark/Input/InputBase.tsx @@ -62,10 +62,10 @@ export const InputBase = <
{prefix}