From a83bd0a5b978b3a6d2c3cf980ab6de5be42169ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Wed, 6 Mar 2024 17:52:16 +0100 Subject: [PATCH] Custom action form should work --- components/actions/action.tsx | 54 +++++++++++--- components/input/function-selector.tsx | 59 ++++++--------- .../input/input-parameter-tuple-array.tsx | 2 +- components/input/input-parameter-tuple.tsx | 2 +- plugins/dualGovernance/hooks/useProposal.tsx | 2 +- plugins/tokenVoting/hooks/useProposal.tsx | 2 +- utils/input-values.ts | 71 +------------------ 7 files changed, 69 insertions(+), 123 deletions(-) diff --git a/components/actions/action.tsx b/components/actions/action.tsx index 8549f4d7..ac4b8fdc 100644 --- a/components/actions/action.tsx +++ b/components/actions/action.tsx @@ -7,14 +7,15 @@ import { Action } from "@/utils/types"; import { useAction } from "@/hooks/useAction"; import { AbiFunction, + AbiParameter, Address, Hex, formatEther, toFunctionSignature, - toHex, } from "viem"; import { compactNumber } from "@/utils/numbers"; import { decodeCamelCase } from "@/utils/case"; +import { InputValue } from "@/utils/input-values"; type ActionCardProps = { action: Action; @@ -26,7 +27,9 @@ type CallParameterFieldType = | bigint | Address | Hex - | boolean; + | boolean + | CallParameterFieldType[] + | { [k: string]: CallParameterFieldType }; export const ActionCard = function ({ action, idx }: ActionCardProps) { const { isLoading, args, functionName, functionAbi } = useAction(action); @@ -158,22 +161,47 @@ const CallParameterField = ({ ); }; -function resolveValue(value: CallParameterFieldType, abiType?: string): string { - if (!abiType) return value.toString(); - else if (abiType === "address") { +function resolveValue( + value: CallParameterFieldType, + abi?: AbiParameter +): string { + if (!abi?.type) { + if (Array.isArray(value)) return value.join(", "); return value.toString(); - } else if (abiType === "bytes32") { - return toHex(value); - } else if (abiType.startsWith("uint") || abiType.startsWith("int")) { + } else if (abi.type === "tuple[]") { + const abiClone = Object.assign({}, { ...abi }); + abiClone.type = abiClone.type.replace(/\[\]$/, ""); + + const items = (value as any as any[]).map((item) => + resolveValue(item, abiClone) + ); + return items.join(", "); + } else if (abi.type === "tuple") { + const result = {} as Record; + const components: AbiParameter[] = (abi as any).components || []; + + for (let i = 0; i < components.length; i++) { + const k = components[i].name!; + result[k] = resolveValue((value as any)[k], components[i]); + } + + return getReadableJson(result); + } else if (abi.type.endsWith("[]")) { + return (value as any as any[]).join(", "); + } else if (abi.type === "address") { + return value as string; + } else if (abi.type === "bytes32") { + return value as string; + } else if (abi.type.startsWith("uint") || abi.type.startsWith("int")) { return value.toString(); - } else if (abiType.startsWith("bool")) { + } else if (abi.type.startsWith("bool")) { return value ? "Yes" : "No"; } return value.toString(); @@ -202,3 +230,9 @@ function resolveAddon( } return (idx + 1).toString(); } + +function getReadableJson(value: Record): string { + const items = Object.keys(value).map((k) => k + ": " + value[k]); + + return "{ " + items.join(", ") + " }"; +} diff --git a/components/input/function-selector.tsx b/components/input/function-selector.tsx index 65f5a951..75ce5975 100644 --- a/components/input/function-selector.tsx +++ b/components/input/function-selector.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import { Hex, encodeFunctionData } from "viem"; import { Button, InputText } from "@aragon/ods"; import { AbiFunction } from "abitype"; @@ -6,7 +6,7 @@ import { Else, If, Then } from "@/components/if"; import { decodeCamelCase } from "@/utils/case"; import { useAlertContext } from "@/context/AlertContext"; import { InputParameter } from "./input-parameter"; -import { InputValue, isValidValue } from "@/utils/input-values"; +import { InputValue } from "@/utils/input-values"; interface IFunctionSelectorProps { abi: AbiFunction[]; @@ -23,6 +23,11 @@ export const FunctionSelector = ({ const [inputValues, setInputValues] = useState([]); const [value, setValue] = useState(""); + useEffect(() => { + // Clean up if another function is selected + setInputValues([]); + }, [abi]); + const onParameterChange = (paramIdx: number, value: InputValue) => { const newInputValues = [...inputValues]; newInputValues[paramIdx] = value; @@ -30,57 +35,33 @@ export const FunctionSelector = ({ }; const onAddAction = () => { - // Validate params if (!abi || !selectedAbiItem) return; - let invalidParams = false; - if (!abi?.length) invalidParams = true; - else if (!selectedAbiItem?.name) invalidParams = true; - else if (selectedAbiItem.inputs.length !== inputValues.length) - invalidParams = true; + // The values we have now are the result of + // validation having happened at the specific components - for (const i in selectedAbiItem.inputs) { - const item = selectedAbiItem.inputs[i]; - if (!isValidValue(inputValues[i], item)) { - invalidParams = true; - break; + for (let i = 0; i < selectedAbiItem.inputs.length; i++) { + if (inputValues[i] === null || inputValues[i] === undefined) { + return addAlert("Invalid parameters", { + description: + "Make sure that you have filled all the parameters and that they contain valid values", + type: "error", + }); } } - invalidParams = invalidParams || !/^[0-9]*$/.test(value); - - if (invalidParams) { - addAlert("Invalid parameters", { - description: "Check that the parameters you entered are correct", - type: "error", - }); - return; - } - - if (["pure", "view"].includes(selectedAbiItem.stateMutability)) { - addAlert("Read only function", { - description: "The action you have added will have no effect", - timeout: 11 * 1000, - }); - } try { - const booleanIdxs = selectedAbiItem.inputs - .map((inp, i) => (inp.type === "bool" ? i : -1)) - .filter((v) => v >= 0); - const args: any[] = [].concat(inputValues as any) as string[]; - for (const i of booleanIdxs) { - if (["false", "False", "no", "No"].includes(args[i])) args[i] = false; - else args[i] = true; - } - const data = encodeFunctionData({ abi, functionName: selectedAbiItem.name, - args, + args: inputValues, }); actionEntered(data, BigInt(value ?? "0")); setInputValues([]); + + // Clean up the form + setSelectedAbiItem(undefined); } catch (err) { console.error(err); addAlert("Invalid parameters", { diff --git a/components/input/input-parameter-tuple-array.tsx b/components/input/input-parameter-tuple-array.tsx index 84fd6e19..f636808e 100644 --- a/components/input/input-parameter-tuple-array.tsx +++ b/components/input/input-parameter-tuple-array.tsx @@ -42,7 +42,7 @@ export const InputParameterTupleArray = ({ }; const components: AbiParameter[] = (abi as any).components || []; - const someMissingName = true || components.some((c) => !c.name); + const someMissingName = components.some((c) => !c.name); return (
diff --git a/components/input/input-parameter-tuple.tsx b/components/input/input-parameter-tuple.tsx index b0a212d0..872f4340 100644 --- a/components/input/input-parameter-tuple.tsx +++ b/components/input/input-parameter-tuple.tsx @@ -45,7 +45,7 @@ export const InputParameterTuple = ({ }; const components: AbiParameter[] = (abi as any).components || []; - const someMissingName = true || components.some((c) => !c.name); + const someMissingName = components.some((c) => !c.name); return (
diff --git a/plugins/dualGovernance/hooks/useProposal.tsx b/plugins/dualGovernance/hooks/useProposal.tsx index 0436642c..bc8418a6 100644 --- a/plugins/dualGovernance/hooks/useProposal.tsx +++ b/plugins/dualGovernance/hooks/useProposal.tsx @@ -81,7 +81,7 @@ export function useProposal( setMetadata(log.args.metadata); }) .catch((err) => { - console.error("Could not fetch the proposal defailt", err); + console.error("Could not fetch the proposal details", err); return null; }); }, [proposalData?.vetoTally]); diff --git a/plugins/tokenVoting/hooks/useProposal.tsx b/plugins/tokenVoting/hooks/useProposal.tsx index a2ce0568..49f0cb41 100644 --- a/plugins/tokenVoting/hooks/useProposal.tsx +++ b/plugins/tokenVoting/hooks/useProposal.tsx @@ -77,7 +77,7 @@ export function useProposal(proposalId: string, autoRefresh = false) { setMetadata(log.args.metadata); }) .catch((err) => { - console.error("Could not fetch the proposal defailt", err); + console.error("Could not fetch the proposal details", err); }); }, [proposalData?.tally]); diff --git a/utils/input-values.ts b/utils/input-values.ts index a851cd8c..e211176e 100644 --- a/utils/input-values.ts +++ b/utils/input-values.ts @@ -1,4 +1,3 @@ -import { AbiParameter } from "viem"; import { decodeCamelCase } from "./case"; export type InputValue = @@ -9,74 +8,6 @@ export type InputValue = | Array | { [k: string]: InputValue }; -export function isValidValue( - value: InputValue, - paramType: string, - components?: AbiParameter[] -): boolean { - if (!value || !paramType) return false; - - // Recursize cases - - if (paramType === "tuple[]") { - // struct array - if (!Array.isArray(value)) return false; - else if (!components) - throw new Error("The components parameter is required for tuples"); - - return !value.some((item, idx) => { - const abi = (paramAbi as any)["components"][idx]; - return !isValidValue(item, abi); - }); - } else if (paramType.endsWith("[]")) { - // plain array - if (!Array.isArray(value)) return false; - const baseType = paramType.replace(/\[\]$/, ""); - - return !value.some((item) => !isValidValue(item, baseType)); - } else if (paramType === "tuple[]") { - // struct - if (!Array.isArray(value)) return false; - else if (!components) - throw new Error("The components parameter is required for tuples"); - - return !value.some((item, idx) => { - const abi = (paramAbi as any)["components"][idx]; - return !isValidValue(item, abi); - }); - } - - // Simple cases - - switch (paramType) { - case "address": - if (typeof value !== "string") return false; - return /^0x[0-9a-fA-F]{40}$/.test(value); - case "bytes": - if (typeof value !== "string") return false; - return value.length % 2 === 0 && /^0x[0-9a-fA-F]*$/.test(value); - case "string": - return typeof value === "string"; - case "bool": - return typeof value === "boolean"; - } - - if (paramType.match(/^bytes[0-9]{1,2}$/)) { - if (typeof value !== "string") return false; - return value.length % 2 === 0 && /^0x[0-9a-fA-F]+$/.test(value); - } else if ( - paramType.match(/^uint[0-9]+$/) || - paramType.match(/^int[0-9]+$/) - ) { - return typeof value === "bigint"; - } - - throw new Error( - "Complex types need to be checked in a higher order function. Got: " + - paramType - ); -} - export function isValidStringValue(value: string, paramType: string): boolean { if (!value || !paramType) return false; @@ -102,7 +33,7 @@ export function isValidStringValue(value: string, paramType: string): boolean { if (paramType.match(/^bytes[0-9]{1,2}$/)) { const len = parseInt(paramType.replace(/^bytes/, "")); - if (value.length !== len * 2) return false; + if (value.length !== 2 + len * 2) return false; return /^0x[0-9a-fA-F]+$/.test(value); } else if (paramType.match(/^uint[0-9]+$/)) { return /^[0-9]*$/.test(value);