Skip to content

Commit

Permalink
Update Support for options and results
Browse files Browse the repository at this point in the history
  • Loading branch information
Nadai2010 committed Nov 25, 2024
1 parent c37256d commit f838e21
Show file tree
Hide file tree
Showing 8 changed files with 182 additions and 32 deletions.
3 changes: 3 additions & 0 deletions packages/nextjs/app/debug/_components/contract/Array.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type ArrayProps = {
setParentForm: (form: Record<string, any>) => void;
parentStateObjectKey: string;
setFormErrorMessage: Dispatch<SetStateAction<FormErrorMessageState>>;
isDisabled?: boolean;
};

export const ArrayInput = ({
Expand All @@ -27,6 +28,7 @@ export const ArrayInput = ({
parentStateObjectKey,
abiParameter,
setFormErrorMessage,
isDisabled,
}: ArrayProps) => {
// array in object representation
const [inputArr, setInputArr] = useState<any>({});
Expand Down Expand Up @@ -63,6 +65,7 @@ export const ArrayInput = ({
<ContractInput
abi={abi}
key={index}
isDisabled={isDisabled}
setForm={(
nextInputRecipe:
| Record<string, any>
Expand Down
17 changes: 14 additions & 3 deletions packages/nextjs/app/debug/_components/contract/ContractInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
isCairoArray,
isCairoBigInt,
isCairoInt,
isCairoOption,
isCairoResult,
isCairoTuple,
isCairoType,
isCairoU256,
Expand All @@ -28,6 +30,7 @@ type ContractInputProps = {
stateObjectKey: string;
paramType: AbiParameter;
setFormErrorMessage: Dispatch<SetStateAction<FormErrorMessageState>>;
isDisabled?: boolean;
};

export const ContractInput = ({
Expand All @@ -37,6 +40,7 @@ export const ContractInput = ({
stateObjectKey,
paramType,
setFormErrorMessage,
isDisabled,
}: ContractInputProps) => {
const inputProps = {
name: stateObjectKey,
Expand All @@ -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 <InputBase {...inputProps} />;
return <InputBase {...inputProps} disabled={isDisabled} />;
} else if (
isCairoInt(paramType.type) ||
isCairoBigInt(paramType.type) ||
Expand All @@ -78,6 +83,7 @@ export const ContractInput = ({
<IntegerInput
{...inputProps}
variant={paramType.type}
disabled={isDisabled}
onError={(errMessage: string | null) =>
setFormErrorMessage((prev) => {
if (!!errMessage)
Expand All @@ -87,8 +93,12 @@ export const ContractInput = ({
}
/>
);
} else if (isCairoType(paramType.type)) {
return <InputBase {...inputProps} />;
} else if (
isCairoType(paramType.type) &&
!isCairoResult(paramType.type) &&
!isCairoOption(paramType.type)
) {
return <InputBase {...inputProps} disabled={isDisabled} />;
} else {
return (
<Struct
Expand All @@ -102,6 +112,7 @@ export const ContractInput = ({
// @ts-ignore
(member) => member.name === paramType.type,
)}
isDisabled={isDisabled}
/>
);
}
Expand Down
59 changes: 36 additions & 23 deletions packages/nextjs/app/debug/_components/contract/Struct.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type StructProps = {
parentStateObjectKey: string;
abiMember?: AbiStruct | AbiEnum;
setFormErrorMessage: Dispatch<SetStateAction<FormErrorMessageState>>;
isDisabled?: boolean;
};

export const Struct = ({
Expand All @@ -22,13 +23,17 @@ export const Struct = ({
abiMember,
abi,
setFormErrorMessage,
isDisabled = false,
}: StructProps) => {
const [form, setForm] = useState<Record<string, any>>(() =>
getInitialTupleFormState(
abiMember ?? { type: "struct", name: "", members: [] },
),
);

// select enum
const [activeVariantIndex, setActiveVariantIndex] = useState(0);

// side effect to transform data before setState
useEffect(() => {
const values = Object.values(form);
Expand All @@ -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({
Expand All @@ -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 (
<div>
<div className="collapse bg-base-200 pl-4 pt-1.5 pb-2 border-2 border-secondary custom-after">
<input type="checkbox" className="min-h-fit peer" />
<div className="collapse-title p-0 min-h-fit peer-checked:mb-2 text-primary-content/50">
<div
className={`collapse bg-base-200 pl-4 pt-1.5 pb-2 border-2 ${isDisabled ? "border-base-100 cursor-not-allowed" : "border-secondary"} custom-after`}
>
{!isDisabled && <input type="checkbox" className="min-h-fit peer" />}
<div
className={`collapse-title p-0 min-h-fit peer-checked:mb-2 text-primary-content/50 ${isDisabled && "cursor-not-allowed"} `}
>
<p className="m-0 p-0 text-[1rem]">{abiMember.type}</p>
</div>
<div className="ml-3 flex-col space-y-4 border-secondary/80 border-l-2 pl-4 collapse-content">
Expand Down Expand Up @@ -104,15 +104,28 @@ export const Struct = ({
index,
);
return (
<ContractInput
setFormErrorMessage={setFormErrorMessage}
abi={abi}
setForm={setForm}
form={form}
key={index}
stateObjectKey={key}
paramType={variant}
/>
<div key={index} className="flex items-center gap-3">
<input
type="checkbox"
name={`radio-${index}`}
className="radio radio-xs radio-secondary"
checked={index === activeVariantIndex}
onChange={() => {}}
onClick={() => {
setActiveVariantIndex(index);
}}
/>
<ContractInput
setFormErrorMessage={setFormErrorMessage}
abi={abi}
setForm={setForm}
form={form}
key={index}
stateObjectKey={key}
paramType={variant}
isDisabled={index !== activeVariantIndex}
/>
</div>
);
})}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -487,4 +487,32 @@ export const abi: any = [
},
],
},
{
type: "enum",
name: "core::option::Option::<core::integer::u256>",
variants: [
{
name: "Some",
type: "core::integer::u256",
},
{
name: "None",
type: "()",
},
],
},
{
type: "enum",
name: "core::result::Result::<core::bool, core::integer::u64>",
variants: [
{
name: "Ok",
type: "core::bool",
},
{
name: "Err",
type: "core::integer::u64",
},
],
},
];
72 changes: 70 additions & 2 deletions packages/nextjs/app/debug/_components/contract/utilsContract.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -101,7 +110,11 @@ export const getArgsAsStringInputFromForm = (form: Record<string, any>) => {
}

// enum & struct
if (key.includes("contracts::")) {
if (
key.includes("contracts::") ||
isCairoResult(key) ||
isCairoOption(key)
) {
type FormStructValue = {
type: string;
value: any;
Expand All @@ -115,6 +128,56 @@ export const getArgsAsStringInputFromForm = (form: Record<string, any>) => {
// 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,
Expand All @@ -127,6 +190,11 @@ export const getArgsAsStringInputFromForm = (form: Record<string, any>) => {
]),
);

if (enumVariants.includes("Some") && enumVariants.includes("None")) {
console.log("options", { restructuredEnum });
console.log("options", "we found an option");
}

return new CairoCustomEnum(restructuredEnum);
}

Expand Down
30 changes: 28 additions & 2 deletions packages/nextjs/app/debug/_components/contract/utilsDisplay.tsx
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit f838e21

Please sign in to comment.