From 464c15f9c8b39caac0a9f86cceb9cbfabbdc7157 Mon Sep 17 00:00:00 2001 From: tom Date: Wed, 7 Jun 2023 14:00:11 -0400 Subject: [PATCH 1/3] base implementation of the selector --- ui/address/AddressContract.tsx | 8 +- ui/address/contract/ContractCode.tsx | 16 +-- ui/address/contract/ContractSourceCode.tsx | 114 +++++++++++++++++---- ui/address/contract/context.tsx | 1 + 4 files changed, 103 insertions(+), 36 deletions(-) diff --git a/ui/address/AddressContract.tsx b/ui/address/AddressContract.tsx index 4820becc98..ad5111293a 100644 --- a/ui/address/AddressContract.tsx +++ b/ui/address/AddressContract.tsx @@ -18,8 +18,12 @@ const TAB_LIST_PROPS = { const AddressContract = ({ addressHash, tabs }: Props) => { const fallback = React.useCallback(() => { const noProviderTabs = tabs.filter(({ id }) => id === 'contact_code'); - return ; - }, [ tabs ]); + return ( + + + + ); + }, [ addressHash, tabs ]); return ( diff --git a/ui/address/contract/ContractCode.tsx b/ui/address/contract/ContractCode.tsx index 02ad6c20a7..368bfa3358 100644 --- a/ui/address/contract/ContractCode.tsx +++ b/ui/address/contract/ContractCode.tsx @@ -212,18 +212,10 @@ const ContractCode = ({ addressHash, noSocket }: Props) => { isLoading={ isPlaceholderData } /> ) } - { data?.source_code && ( - - ) } + { data?.compiler_settings ? ( ['id']; + +function getEditorData(contractInfo: SmartContract | undefined) { + if (!contractInfo || !contractInfo.source_code) { + return undefined; + } + + const defaultName = contractInfo.is_vyper_contract ? '/index.vy' : '/index.sol'; + return [ + { file_path: formatFilePath(contractInfo.file_path || defaultName), source_code: contractInfo.source_code }, + ...(contractInfo.additional_sources || []).map((source) => ({ ...source, file_path: formatFilePath(source.file_path) })), + ]; +} + interface Props { - data: string; - hasSol2Yml: boolean; - address?: string; - isViper: boolean; - filePath?: string; - additionalSource?: SmartContract['additional_sources']; - remappings?: Array; - isLoading?: boolean; + address?: string; // todo_tom need address of proxy contract + isLoading?: boolean; // todo_tom should be true if proxyInfo is not loaded } -const ContractSourceCode = ({ data, hasSol2Yml, address, isViper, filePath, additionalSource, remappings, isLoading }: Props) => { +// todo_tom fix mobile layout +const ContractSourceCode = ({ address, isLoading }: Props) => { + const [ sourceType, setSourceType ] = React.useState('primary'); + + const { contractInfo: primaryContract, proxyInfo: secondaryContract } = useContractContext(); + + const editorDataPrimary = React.useMemo(() => { + return getEditorData(primaryContract); + }, [ primaryContract ]); + + const editorDataSecondary = React.useMemo(() => { + return getEditorData(secondaryContract); + }, [ secondaryContract ]); + + const activeContract = sourceType === 'secondary' ? secondaryContract : primaryContract; + const activeContractData = sourceType === 'secondary' ? editorDataSecondary : editorDataPrimary; + const heading = ( Contract source code - ({ isViper ? 'Vyper' : 'Solidity' }) + ({ activeContract?.is_vyper_contract ? 'Vyper' : 'Solidity' }) ); - const diagramLink = hasSol2Yml && address ? ( + const diagramLink = activeContract?.can_be_visualized_via_sol2uml && address ? ( ) : null; - const editorData = React.useMemo(() => { - const defaultName = isViper ? '/index.vy' : '/index.sol'; - return [ - { file_path: formatFilePath(filePath || defaultName), source_code: data }, - ...(additionalSource || []).map((source) => ({ ...source, file_path: formatFilePath(source.file_path) })) ]; - }, [ additionalSource, data, filePath, isViper ]); - - const copyToClipboard = editorData.length === 1 ? - : + const copyToClipboard = activeContractData?.length === 1 ? + : null; + const handleSelectChange = React.useCallback((event: React.ChangeEvent) => { + setSourceType(event.target.value as SourceCodeType); + }, []); + + const editorSourceTypeSelector = secondaryContract?.source_code ? ( + + ) : null; + + const content = (() => { + if (isLoading) { + return ; + } + + if (!editorDataPrimary) { + return null; + } + + return ( + <> + + + + { editorDataSecondary && ( + + + + ) } + + ); + })(); + + if (!editorDataPrimary) { + return null; + } + return (
{ heading } + { editorSourceTypeSelector } { diagramLink } { copyToClipboard } - { isLoading ? : } + { content }
); }; diff --git a/ui/address/contract/context.tsx b/ui/address/contract/context.tsx index 226b0bbb9d..6f26307aa2 100644 --- a/ui/address/contract/context.tsx +++ b/ui/address/contract/context.tsx @@ -46,6 +46,7 @@ export function ContractContextProvider({ addressHash, children }: ProviderProps }, }); + // todo_tom check custom abi case const { data: customInfo } = useApiQuery('contract_methods_write', { pathParams: { hash: addressHash }, queryParams: { is_custom_abi: 'true' }, From 058575d914761cb6462787fe430775fac26bf992 Mon Sep 17 00:00:00 2001 From: tom Date: Wed, 7 Jun 2023 16:21:46 -0400 Subject: [PATCH 2/3] refactor and remove context usage --- ui/address/contract/ContractCode.tsx | 10 +-- ui/address/contract/ContractSourceCode.tsx | 82 ++++++++++++++-------- 2 files changed, 60 insertions(+), 32 deletions(-) diff --git a/ui/address/contract/ContractCode.tsx b/ui/address/contract/ContractCode.tsx index 368bfa3358..2604d082d5 100644 --- a/ui/address/contract/ContractCode.tsx +++ b/ui/address/contract/ContractCode.tsx @@ -212,10 +212,12 @@ const ContractCode = ({ addressHash, noSocket }: Props) => { isLoading={ isPlaceholderData } /> ) } - + { data?.is_verified && ( + + ) } { data?.compiler_settings ? ( { +const ContractSourceCode = ({ address, implementationAddress }: Props) => { const [ sourceType, setSourceType ] = React.useState('primary'); - const { contractInfo: primaryContract, proxyInfo: secondaryContract } = useContractContext(); - - const editorDataPrimary = React.useMemo(() => { - return getEditorData(primaryContract); - }, [ primaryContract ]); - - const editorDataSecondary = React.useMemo(() => { - return getEditorData(secondaryContract); - }, [ secondaryContract ]); - - const activeContract = sourceType === 'secondary' ? secondaryContract : primaryContract; - const activeContractData = sourceType === 'secondary' ? editorDataSecondary : editorDataPrimary; + const primaryContractQuery = useApiQuery('contract', { + pathParams: { hash: address }, + queryOptions: { + enabled: Boolean(address), + refetchOnMount: false, + placeholderData: stubs.CONTRACT_CODE_VERIFIED, + }, + }); + + const secondaryContractQuery = useApiQuery('contract', { + pathParams: { hash: implementationAddress }, + queryOptions: { + enabled: Boolean(implementationAddress), + refetchOnMount: false, + placeholderData: stubs.CONTRACT_CODE_VERIFIED, + }, + }); + + const isLoading = implementationAddress ? + primaryContractQuery.isPlaceholderData || secondaryContractQuery.isPlaceholderData : + primaryContractQuery.isPlaceholderData; + + const primaryEditorData = React.useMemo(() => { + return getEditorData(primaryContractQuery.data); + }, [ primaryContractQuery.data ]); + + const secondaryEditorData = React.useMemo(() => { + return getEditorData(secondaryContractQuery.data); + }, [ secondaryContractQuery.data ]); + + const activeContract = sourceType === 'secondary' ? secondaryContractQuery.data : primaryContractQuery.data; + const activeContractData = sourceType === 'secondary' ? secondaryEditorData : primaryEditorData; const heading = ( @@ -59,10 +78,17 @@ const ContractSourceCode = ({ address, isLoading }: Props) => { ); - const diagramLink = activeContract?.can_be_visualized_via_sol2uml && address ? ( + const diagramLinkAddress = (() => { + if (!activeContract?.can_be_visualized_via_sol2uml) { + return; + } + return sourceType === 'secondary' ? implementationAddress : address; + })(); + + const diagramLink = diagramLinkAddress ? ( @@ -70,7 +96,7 @@ const ContractSourceCode = ({ address, isLoading }: Props) => { - ) : null; + ) : ; const copyToClipboard = activeContractData?.length === 1 ? : @@ -80,15 +106,15 @@ const ContractSourceCode = ({ address, isLoading }: Props) => { setSourceType(event.target.value as SourceCodeType); }, []); - const editorSourceTypeSelector = secondaryContract?.source_code ? ( + const editorSourceTypeSelector = !secondaryContractQuery.isPlaceholderData && secondaryContractQuery.data?.source_code ? ( @@ -99,25 +125,25 @@ const ContractSourceCode = ({ address, isLoading }: Props) => { return ; } - if (!editorDataPrimary) { + if (!primaryEditorData) { return null; } return ( <> - + - { editorDataSecondary && ( + { secondaryEditorData && ( - + ) } ); })(); - if (!editorDataPrimary) { + if (!primaryEditorData) { return null; } From 22990d6ad815d46ac5c679ef27904dc65aaf6e0f Mon Sep 17 00:00:00 2001 From: tom Date: Wed, 7 Jun 2023 16:38:27 -0400 Subject: [PATCH 3/3] remove contract context --- ui/address/AddressContract.tsx | 13 ++--- ui/address/contract/ContractWrite.tsx | 21 ++------ .../{context.tsx => useContractAbi.tsx} | 48 ++++++------------- ui/pages/Address.tsx | 4 +- ui/pages/Token.tsx | 2 +- 5 files changed, 26 insertions(+), 62 deletions(-) rename ui/address/contract/{context.tsx => useContractAbi.tsx} (52%) diff --git a/ui/address/AddressContract.tsx b/ui/address/AddressContract.tsx index ad5111293a..32bd6e8baf 100644 --- a/ui/address/AddressContract.tsx +++ b/ui/address/AddressContract.tsx @@ -2,7 +2,6 @@ import React from 'react'; import type { RoutedSubTab } from 'ui/shared/Tabs/types'; -import { ContractContextProvider } from 'ui/address/contract/context'; import RoutedTabs from 'ui/shared/Tabs/RoutedTabs'; import Web3ModalProvider from 'ui/shared/Web3ModalProvider'; @@ -15,21 +14,17 @@ const TAB_LIST_PROPS = { columnGap: 3, }; -const AddressContract = ({ addressHash, tabs }: Props) => { +const AddressContract = ({ tabs }: Props) => { const fallback = React.useCallback(() => { const noProviderTabs = tabs.filter(({ id }) => id === 'contact_code'); return ( - - - + ); - }, [ addressHash, tabs ]); + }, [ tabs ]); return ( - - - + ); }; diff --git a/ui/address/contract/ContractWrite.tsx b/ui/address/contract/ContractWrite.tsx index ddbe20a58e..529c9d21a0 100644 --- a/ui/address/contract/ContractWrite.tsx +++ b/ui/address/contract/ContractWrite.tsx @@ -9,12 +9,12 @@ import ContractMethodsAccordion from 'ui/address/contract/ContractMethodsAccordi import ContentLoader from 'ui/shared/ContentLoader'; import DataFetchAlert from 'ui/shared/DataFetchAlert'; -import { useContractContext } from './context'; import ContractConnectWallet from './ContractConnectWallet'; import ContractCustomAbiAlert from './ContractCustomAbiAlert'; import ContractImplementationAddress from './ContractImplementationAddress'; import ContractMethodCallable from './ContractMethodCallable'; import ContractWriteResult from './ContractWriteResult'; +import useContractAbi from './useContractAbi'; import { getNativeCoinValue } from './utils'; interface Props { @@ -39,18 +39,7 @@ const ContractWrite = ({ addressHash, isProxy, isCustomAbi }: Props) => { }, }); - const { contractInfo, customInfo, proxyInfo } = useContractContext(); - const abi = (() => { - if (isProxy) { - return proxyInfo?.abi; - } - - if (isCustomAbi) { - return customInfo?.abi; - } - - return contractInfo?.abi; - })(); + const contractAbi = useContractAbi({ addressHash, isProxy, isCustomAbi }); const handleMethodFormSubmit = React.useCallback(async(item: SmartContractWriteMethod, args: Array>) => { if (!isConnected) { @@ -61,7 +50,7 @@ const ContractWrite = ({ addressHash, isProxy, isCustomAbi }: Props) => { await switchNetworkAsync?.(Number(config.network.id)); } - if (!abi) { + if (!contractAbi) { throw new Error('Something went wrong. Try again later.'); } @@ -84,14 +73,14 @@ const ContractWrite = ({ addressHash, isProxy, isCustomAbi }: Props) => { const hash = await walletClient?.writeContract({ args: _args, - abi: abi, + abi: contractAbi, functionName: methodName, address: addressHash as `0x${ string }`, value: value as undefined, }); return { hash }; - }, [ isConnected, chain, abi, walletClient, addressHash, switchNetworkAsync ]); + }, [ isConnected, chain, contractAbi, walletClient, addressHash, switchNetworkAsync ]); const renderContent = React.useCallback((item: SmartContractWriteMethod, index: number, id: number) => { return ( diff --git a/ui/address/contract/context.tsx b/ui/address/contract/useContractAbi.tsx similarity index 52% rename from ui/address/contract/context.tsx rename to ui/address/contract/useContractAbi.tsx index 6f26307aa2..7f85bb4d8e 100644 --- a/ui/address/contract/context.tsx +++ b/ui/address/contract/useContractAbi.tsx @@ -1,29 +1,18 @@ import { useQueryClient } from '@tanstack/react-query'; +import type { Abi } from 'abitype'; import React from 'react'; import type { Address } from 'types/api/address'; -import type { SmartContract } from 'types/api/contract'; import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery'; -type ProviderProps = { +interface Params { addressHash?: string; - children: React.ReactNode; + isProxy?: boolean; + isCustomAbi?: boolean; } -type TContractContext = { - contractInfo: SmartContract | undefined; - proxyInfo: SmartContract | undefined; - customInfo: SmartContract | undefined; -}; - -const ContractContext = React.createContext({ - proxyInfo: undefined, - contractInfo: undefined, - customInfo: undefined, -}); - -export function ContractContextProvider({ addressHash, children }: ProviderProps) { +export default function useContractAbi({ addressHash, isProxy, isCustomAbi }: Params): Abi | undefined { const queryClient = useQueryClient(); const { data: contractInfo } = useApiQuery('contract', { @@ -46,7 +35,6 @@ export function ContractContextProvider({ addressHash, children }: ProviderProps }, }); - // todo_tom check custom abi case const { data: customInfo } = useApiQuery('contract_methods_write', { pathParams: { hash: addressHash }, queryParams: { is_custom_abi: 'true' }, @@ -56,23 +44,15 @@ export function ContractContextProvider({ addressHash, children }: ProviderProps }, }); - const value = React.useMemo(() => ({ - proxyInfo, - contractInfo, - customInfo, - } as TContractContext), [ proxyInfo, contractInfo, customInfo ]); + return React.useMemo(() => { + if (isProxy) { + return proxyInfo?.abi ?? undefined; + } - return ( - - { children } - - ); -} + if (isCustomAbi) { + return customInfo; + } -export function useContractContext() { - const context = React.useContext(ContractContext); - if (context === undefined) { - throw new Error('useContractContext must be used within a ContractContextProvider'); - } - return context; + return contractInfo?.abi ?? undefined; + }, [ contractInfo?.abi, customInfo, isCustomAbi, isProxy, proxyInfo?.abi ]); } diff --git a/ui/pages/Address.tsx b/ui/pages/Address.tsx index 5961a93c16..5245924a47 100644 --- a/ui/pages/Address.tsx +++ b/ui/pages/Address.tsx @@ -86,11 +86,11 @@ const AddressPageContent = () => { return 'Contract'; }, - component: , + component: , subTabs: contractTabs.map(tab => tab.id), } : undefined, ].filter(Boolean); - }, [ addressQuery.data, contractTabs, hash ]); + }, [ addressQuery.data, contractTabs ]); const tags = ( { return 'Contract'; }, - component: , + component: , subTabs: contractTabs.map(tab => tab.id), } : undefined, ].filter(Boolean);