From ddc2e1f34d4dd2c6a74bb9a95ac60e7c4df0b924 Mon Sep 17 00:00:00 2001 From: Henry Palacios Date: Tue, 14 Nov 2023 20:01:57 -0300 Subject: [PATCH 1/7] Managing Errors on Read methods --- src/hooks/useContractCaller.ts | 68 ++++++++++++++++--- src/services/useink/utils/decodeError.ts | 36 ++++++++++ src/services/useink/utils/pickDecodedError.ts | 22 ------ src/utils/contractExecResult.ts | 6 +- .../ReadMethodsForm.tsx | 55 +++++++-------- .../ContractInteractionForm/index.tsx | 2 + .../ContractDetailView/useDryRunExecution.tsx | 3 +- .../AccountSelect/index.tsx | 5 +- 8 files changed, 132 insertions(+), 65 deletions(-) create mode 100644 src/services/useink/utils/decodeError.ts delete mode 100644 src/services/useink/utils/pickDecodedError.ts diff --git a/src/hooks/useContractCaller.ts b/src/hooks/useContractCaller.ts index e1e59b1c..c0b0e0ae 100644 --- a/src/hooks/useContractCaller.ts +++ b/src/hooks/useContractCaller.ts @@ -1,22 +1,72 @@ -import { useMemo } from 'react' -import { useCall } from 'useink' -import { AbiMessage, ContractPromise } from '@/services/substrate/types' +import { useEffect, useMemo, useState } from 'react' +import { Call, useCall } from 'useink' +import { + AbiMessage, + ContractPromise, + Registry +} from '@/services/substrate/types' import { useNetworkApi } from './useNetworkApi' +import { getDecodedOutput, stringify } from '@/utils/contractExecResult' +import { decodeError } from '@/services/useink/utils/decodeError' -export function useContractCaller( - contract: ContractPromise, - method: AbiMessage['method'] -) { +interface Props { + contractPromise: ContractPromise + abiMessage: AbiMessage + substrateRegistry: Registry +} + +interface UseContractCallerReturn { + caller: Call + outcome: string + error: string | undefined +} + +export function useContractCaller({ + contractPromise: contract, + abiMessage, + substrateRegistry +}: Props): UseContractCallerReturn { const { network: chainId } = useNetworkApi() + const [outcome, setOutcome] = useState('') + const [error, setError] = useState() + const { method } = abiMessage const callContractArgs = useMemo(() => { return { chainContract: { contract, chainId }, method } }, [chainId, contract, method]) - const caller = useCall( + const caller = useCall( callContractArgs.chainContract, callContractArgs.method ) - return { caller } + useEffect(() => { + setError(undefined) + + if (caller.result?.ok) { + const { decodedOutput } = getDecodedOutput( + { + debugMessage: caller.result.value.raw.debugMessage, + result: caller.result.value.raw.result + }, + abiMessage, + substrateRegistry + ) + setOutcome(decodedOutput) + } else { + setError( + decodeError( + caller.result?.error, + callContractArgs.chainContract.contract + ) + ) + } + }, [ + abiMessage, + callContractArgs.chainContract, + caller.result, + substrateRegistry + ]) + + return { caller, outcome, error } } diff --git a/src/services/useink/utils/decodeError.ts b/src/services/useink/utils/decodeError.ts new file mode 100644 index 00000000..03ad2134 --- /dev/null +++ b/src/services/useink/utils/decodeError.ts @@ -0,0 +1,36 @@ +import { + ContractPromise, + DispatchError, + RegistryError +} from '@/services/substrate/types.js' + +export type RegistryErrorMethod = string + +export const getRegistryError = ( + error: DispatchError | undefined, + { api }: ContractPromise +): RegistryError | undefined => { + if (!error?.isModule) return + + return api?.registry.findMetaError(error.asModule) +} + +const formatErrorMessage = (registryError: RegistryError): string => + `${registryError.section}.${registryError.method}: ${registryError.docs}` + +export const decodeError = ( + dispatchError: DispatchError | undefined, + chainContract: ContractPromise | undefined, + moduleMessages?: Record, + defaultMessage?: string +): string | undefined => { + if (!chainContract) return undefined + const registryError = getRegistryError(dispatchError, chainContract) + if (!registryError) return undefined + + return ( + moduleMessages?.[registryError.method] || + defaultMessage || + formatErrorMessage(registryError) + ) +} diff --git a/src/services/useink/utils/pickDecodedError.ts b/src/services/useink/utils/pickDecodedError.ts deleted file mode 100644 index e75d82c2..00000000 --- a/src/services/useink/utils/pickDecodedError.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { - CallResult, - Contract, - RegistryErrorMethod, - decodeError -} from 'useink/dist/core' - -/// pickDecodedError is a helper function to quickly get a decoded error from a call. -/// -/// // e.g. -/// const call = useCall(contract, 'foo'); -/// pickDecodedError(call, contract); -export function pickDecodedError( - call: CallResult | undefined, - contract: Contract, - moduleMessages?: Record, - defaultMessage?: string -): string | undefined { - const { result } = call || {} - if (!result || result?.ok) return - return decodeError(result.error, contract, moduleMessages, defaultMessage) -} diff --git a/src/utils/contractExecResult.ts b/src/utils/contractExecResult.ts index b2d6a7c9..02841b42 100644 --- a/src/utils/contractExecResult.ts +++ b/src/utils/contractExecResult.ts @@ -24,7 +24,7 @@ function getReturnTypeName(type: TypeDef | null | undefined): string { return type?.lookupName || type?.type || '' } -function stringify(obj: unknown): string { +export function stringify(obj: unknown): string { return JSON5.stringify(obj, null, 2) } @@ -53,6 +53,10 @@ function extractOutcome(returnValue: AnyJson): AnyJson { } function getOutcomeText(outcome: AnyJson): string { + if (isContractResult(outcome) && outcome.Ok === null) { + return 'null' + } + if (!isContractResult(outcome)) { return typeof outcome === 'object' && outcome !== null ? stringify(outcome) diff --git a/src/view/ContractDetailView/ContractInteractionForm/ReadMethodsForm.tsx b/src/view/ContractDetailView/ContractInteractionForm/ReadMethodsForm.tsx index 8d6d72ad..cccd7a04 100644 --- a/src/view/ContractDetailView/ContractInteractionForm/ReadMethodsForm.tsx +++ b/src/view/ContractDetailView/ContractInteractionForm/ReadMethodsForm.tsx @@ -1,12 +1,17 @@ -import { useEffect, useState } from 'react' +import { useEffect } from 'react' import { ContractInteractionProps } from '.' -import { Box, CircularProgress, Stack, Typography } from '@mui/material' +import { + Box, + CircularProgress, + Stack, + Typography, + FormHelperText +} from '@mui/material' import { MethodDocumentation } from '../MethodDocumentation' import { AbiParam } from '@/services/substrate/types' import { ButtonCall } from './styled' import { useContractCaller } from '@/hooks/useContractCaller' import { CopyBlock, atomOneDark } from 'react-code-blocks' -import { getDecodedOutput } from '@/utils/contractExecResult' type Props = React.PropsWithChildren< Omit & { @@ -24,8 +29,11 @@ export function ReadMethodsForm({ substrateRegistry, expanded }: Props) { - const { caller } = useContractCaller(contractPromise, abiMessage.method) - const [outcome, setOutcome] = useState('') + const { caller, outcome, error } = useContractCaller({ + contractPromise, + abiMessage, + substrateRegistry + }) useEffect(() => { if (!expanded) return @@ -33,20 +41,6 @@ export function ReadMethodsForm({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [inputData, expanded]) - useEffect(() => { - if (caller.result?.ok) { - const { decodedOutput, isError } = getDecodedOutput( - { - debugMessage: caller.result.value.raw.debugMessage, - result: caller.result.value.raw.result - }, - abiMessage, - substrateRegistry - ) - setOutcome(decodedOutput) - } - }, [abiMessage, caller.result, substrateRegistry]) - return ( ) : ( - + <> + + {error && ( + + {error} + + )} + )} caller.send(inputData)}>Recall diff --git a/src/view/ContractDetailView/ContractInteractionForm/index.tsx b/src/view/ContractDetailView/ContractInteractionForm/index.tsx index 5cc82777..42a82f52 100644 --- a/src/view/ContractDetailView/ContractInteractionForm/index.tsx +++ b/src/view/ContractDetailView/ContractInteractionForm/index.tsx @@ -40,6 +40,7 @@ export function ContractInteractionForm({ abiParams={abiParams} inputData={inputData} substrateRegistry={substrateRegistry} + expanded={expanded} > + ) if (!currentAccount) From 529e82d4118904119af03690aab5d6ce45818ac9 Mon Sep 17 00:00:00 2001 From: Henry Palacios Date: Wed, 15 Nov 2023 10:44:02 -0300 Subject: [PATCH 2/7] Adding write methods --- .../ContractInteractionForm/WriteMethodsForm.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/view/ContractDetailView/ContractInteractionForm/WriteMethodsForm.tsx b/src/view/ContractDetailView/ContractInteractionForm/WriteMethodsForm.tsx index aa4ed1d8..b649d086 100644 --- a/src/view/ContractDetailView/ContractInteractionForm/WriteMethodsForm.tsx +++ b/src/view/ContractDetailView/ContractInteractionForm/WriteMethodsForm.tsx @@ -1,18 +1,17 @@ import { Box, CircularProgress, Stack, Typography } from '@mui/material' import { ContractInteractionProps } from '.' import { AbiParam } from '@/services/substrate/types' +import { useDryRunExecution } from '../useDryRunExecution' +import { MethodDocumentation } from '../MethodDocumentation' type Props = React.PropsWithChildren< - Pick< - ContractInteractionProps, - 'abiMessage' | 'expanded' | 'contractPromise' - > & { + Omit & { abiParams: AbiParam[] inputData: unknown[] | undefined } > -export function WriteMethodsForm({ children, abiParams }: Props) { +export function WriteMethodsForm({ children, abiParams, abiMessage }: Props) { // const { outcome, executeDryRun } = useDryRunExecution({ // contractPromise, // message: abiMessage, @@ -59,7 +58,7 @@ export function WriteMethodsForm({ children, abiParams }: Props) { */} - {/* */} + ) From f2f0c4a59f8789c362b80dfeec53845bf7db2f9f Mon Sep 17 00:00:00 2001 From: Henry Palacios Date: Wed, 15 Nov 2023 10:53:00 -0300 Subject: [PATCH 3/7] Add registry to WriteForm componente --- src/view/ContractDetailView/ContractInteractionForm/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/view/ContractDetailView/ContractInteractionForm/index.tsx b/src/view/ContractDetailView/ContractInteractionForm/index.tsx index 42a82f52..f6500b50 100644 --- a/src/view/ContractDetailView/ContractInteractionForm/index.tsx +++ b/src/view/ContractDetailView/ContractInteractionForm/index.tsx @@ -55,6 +55,7 @@ export function ContractInteractionForm({ contractPromise={contractPromise} abiParams={abiParams} inputData={inputData} + substrateRegistry={substrateRegistry} expanded={expanded} > Date: Wed, 15 Nov 2023 10:54:44 -0300 Subject: [PATCH 4/7] Removing unused function --- src/view/ContractDetailView/useDryRunExecution.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/view/ContractDetailView/useDryRunExecution.tsx b/src/view/ContractDetailView/useDryRunExecution.tsx index 233c6e94..b7892179 100644 --- a/src/view/ContractDetailView/useDryRunExecution.tsx +++ b/src/view/ContractDetailView/useDryRunExecution.tsx @@ -2,7 +2,6 @@ import { useCallback, useMemo, useState } from 'react' import { AbiMessage, ContractPromise } from '@/services/substrate/types' import { useGetDryRun } from '@/hooks/useGetDryRun' import { useDebouncedEffect } from '@/hooks/useDebouncedEffect' -import { pickDecodedError } from '@/services/useink/utils/pickDecodedError' interface UseDryRunExecutionProps { contractPromise: ContractPromise From f29e5d2c9849f24ea28028693c7271c325ec8873 Mon Sep 17 00:00:00 2001 From: Henry Palacios Date: Wed, 15 Nov 2023 15:34:12 -0300 Subject: [PATCH 5/7] Executing Dry Running --- .../WriteMethodsForm.tsx | 31 ++++++++++++------- src/view/ContractDetailView/index.tsx | 2 +- .../ContractDetailView/useDryRunExecution.tsx | 1 - 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/view/ContractDetailView/ContractInteractionForm/WriteMethodsForm.tsx b/src/view/ContractDetailView/ContractInteractionForm/WriteMethodsForm.tsx index b649d086..879f547f 100644 --- a/src/view/ContractDetailView/ContractInteractionForm/WriteMethodsForm.tsx +++ b/src/view/ContractDetailView/ContractInteractionForm/WriteMethodsForm.tsx @@ -3,6 +3,8 @@ import { ContractInteractionProps } from '.' import { AbiParam } from '@/services/substrate/types' import { useDryRunExecution } from '../useDryRunExecution' import { MethodDocumentation } from '../MethodDocumentation' +import { ButtonCall } from './styled' +import { CopyBlock, atomOneDark } from 'react-code-blocks' type Props = React.PropsWithChildren< Omit & { @@ -11,13 +13,19 @@ type Props = React.PropsWithChildren< } > -export function WriteMethodsForm({ children, abiParams, abiMessage }: Props) { - // const { outcome, executeDryRun } = useDryRunExecution({ - // contractPromise, - // message: abiMessage, - // params: inputData, - // autoRun: false - // }) +export function WriteMethodsForm({ + children, + abiParams, + abiMessage, + contractPromise, + inputData +}: Props) { + const { outcome, executeDryRun, isSubmitting } = useDryRunExecution({ + contractPromise, + message: abiMessage, + params: inputData, + autoRun: false + }) return ( Outcome - {/* + - {caller.isSubmitting ? ( + {isSubmitting ? ( ) : ( )} - caller.send(inputData)}>Call - */} + executeDryRun(inputData)}>Call + diff --git a/src/view/ContractDetailView/index.tsx b/src/view/ContractDetailView/index.tsx index 9ac81219..9ba42b11 100644 --- a/src/view/ContractDetailView/index.tsx +++ b/src/view/ContractDetailView/index.tsx @@ -249,7 +249,7 @@ export default function ContractDetail({ userContract }: Props): JSX.Element { - Added by + By {''} {truncateAddress(userContract.userAddress, 4)} diff --git a/src/view/ContractDetailView/useDryRunExecution.tsx b/src/view/ContractDetailView/useDryRunExecution.tsx index b7892179..463310da 100644 --- a/src/view/ContractDetailView/useDryRunExecution.tsx +++ b/src/view/ContractDetailView/useDryRunExecution.tsx @@ -28,7 +28,6 @@ export function useDryRunExecution({ const executeDryRun = useCallback(async () => { const result = await dryRun.send(memoizedParams) - console.log('__dryRun', dryRun) if (result?.ok) { setOutcome( `Contract call will be successful executed with ${result.value.partialFee.toString()} fee` From 2c67f0de165beff4f0dd699dbc4ab414b62557dd Mon Sep 17 00:00:00 2001 From: Henry Palacios Date: Wed, 15 Nov 2023 15:35:22 -0300 Subject: [PATCH 6/7] Fixing ts errors --- .../ContractInteractionForm/WriteMethodsForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/view/ContractDetailView/ContractInteractionForm/WriteMethodsForm.tsx b/src/view/ContractDetailView/ContractInteractionForm/WriteMethodsForm.tsx index 879f547f..3fe88540 100644 --- a/src/view/ContractDetailView/ContractInteractionForm/WriteMethodsForm.tsx +++ b/src/view/ContractDetailView/ContractInteractionForm/WriteMethodsForm.tsx @@ -63,7 +63,7 @@ export function WriteMethodsForm({ /> )} - executeDryRun(inputData)}>Call + Call From 99de7185b331979f9bcf532d396c71f6d153f6bf Mon Sep 17 00:00:00 2001 From: Henry Palacios Date: Wed, 15 Nov 2023 16:00:24 -0300 Subject: [PATCH 7/7] Adding error message --- .../ContractInteractionForm/ReadMethodsForm.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/view/ContractDetailView/ContractInteractionForm/ReadMethodsForm.tsx b/src/view/ContractDetailView/ContractInteractionForm/ReadMethodsForm.tsx index 70a7148f..7d90870d 100644 --- a/src/view/ContractDetailView/ContractInteractionForm/ReadMethodsForm.tsx +++ b/src/view/ContractDetailView/ContractInteractionForm/ReadMethodsForm.tsx @@ -1,6 +1,6 @@ import { useEffect } from 'react' import { ContractInteractionProps } from '.' -import { Box, Stack, Typography } from '@mui/material' +import { Box, FormHelperText, Stack, Typography } from '@mui/material' import { MethodDocumentation } from '../MethodDocumentation' import { AbiParam } from '@/services/substrate/types' import { ButtonCall } from './styled' @@ -91,6 +91,11 @@ export function ReadMethodsForm({ Recall + {error && ( + + {error} + + )}