Commit 7c00d780 authored by tom's avatar tom

form config

parent 988d3f6f
......@@ -17,7 +17,7 @@ import type {
import type { AddressesResponse } from 'types/api/addresses';
import type { BlocksResponse, BlockTransactionsResponse, Block, BlockFilters } from 'types/api/block';
import type { ChartMarketResponse, ChartTransactionResponse } from 'types/api/charts';
import type { SmartContract, SmartContractReadMethod, SmartContractWriteMethod } from 'types/api/contract';
import type { SmartContract, SmartContractReadMethod, SmartContractWriteMethod, SmartContractVerificationConfig } from 'types/api/contract';
import type { IndexingStatus } from 'types/api/indexingStatus';
import type { InternalTransactionsResponse } from 'types/api/internalTransaction';
import type { LogsResponseTx, LogsResponseAddress } from 'types/api/log';
......@@ -204,6 +204,9 @@ export const RESOURCES = {
contract_methods_write_proxy: {
path: '/api/v2/smart-contracts/:id/methods-write-proxy',
},
contract_verification_config: {
path: '/api/v2/smart-contracts/verification/config',
},
// TOKEN
token: {
......@@ -356,6 +359,7 @@ Q extends 'contract_methods_read' ? Array<SmartContractReadMethod> :
Q extends 'contract_methods_read_proxy' ? Array<SmartContractReadMethod> :
Q extends 'contract_methods_write' ? Array<SmartContractWriteMethod> :
Q extends 'contract_methods_write_proxy' ? Array<SmartContractWriteMethod> :
Q extends 'contract_verification_config' ? SmartContractVerificationConfig :
never;
/* eslint-enable @typescript-eslint/indent */
......
......@@ -113,3 +113,19 @@ export interface SmartContractQueryMethodReadError {
}
export type SmartContractQueryMethodRead = SmartContractQueryMethodReadSuccess | SmartContractQueryMethodReadError;
// VERIFICATION
export type SmartContractVerificationMethod = 'flattened_code' | 'standard_input' | 'sourcify' | 'multi_part' | 'vyper_multi_part';
export interface SmartContractVerificationConfigRaw {
solidity_compiler_versions: Array<string>;
solidity_evm_versions: Array<string>;
verification_options: Array<string>;
vyper_compiler_versions: Array<string>;
vyper_evm_versions: Array<string>;
}
export interface SmartContractVerificationConfig extends SmartContractVerificationConfigRaw {
verification_options: Array<SmartContractVerificationMethod>;
}
import { test, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import type { SmartContractVerificationConfig } from 'types/api/contract';
import TestApp from 'playwright/TestApp';
import ContractVerificationForm from './ContractVerificationForm';
......@@ -11,10 +13,48 @@ const hooksConfig = {
},
};
const formConfig: SmartContractVerificationConfig = {
solidity_compiler_versions: [
'v0.8.17+commit.8df45f5f',
'v0.8.16+commit.07a7930e',
'v0.8.15+commit.e14f2714',
'v0.8.18-nightly.2022.11.23+commit.eb2f874e',
'v0.8.17-nightly.2022.8.24+commit.22a0c46e',
'v0.8.16-nightly.2022.7.6+commit.b6f11b33',
],
solidity_evm_versions: [
'default',
'london',
'berlin',
],
verification_options: [
'flattened_code',
'standard_input',
'sourcify',
'multi_part',
'vyper_multi_part',
],
vyper_compiler_versions: [
'v0.3.7+commit.6020b8bb',
'v0.3.1+commit.0463ea4c',
'v0.3.0+commit.8a23feb',
'v0.2.16+commit.59e1bdd',
'v0.2.3+commit.006968f',
'v0.2.2+commit.337c2ef',
'v0.1.0-beta.17+commit.0671b7b',
],
vyper_evm_versions: [
'byzantium',
'constantinople',
'petersburg',
'istanbul',
],
};
test('flatten source code method +@dark-mode +@mobile', async({ mount, page }) => {
const component = await mount(
<TestApp>
<ContractVerificationForm/>
<ContractVerificationForm config={ formConfig }/>
</TestApp>,
{ hooksConfig },
);
......@@ -30,7 +70,7 @@ test('flatten source code method +@dark-mode +@mobile', async({ mount, page }) =
test('standard input json method', async({ mount, page }) => {
const component = await mount(
<TestApp>
<ContractVerificationForm/>
<ContractVerificationForm config={ formConfig }/>
</TestApp>,
{ hooksConfig },
);
......@@ -43,7 +83,7 @@ test('standard input json method', async({ mount, page }) => {
test('sourcify method +@dark-mode +@mobile', async({ mount, page }) => {
const component = await mount(
<TestApp>
<ContractVerificationForm/>
<ContractVerificationForm config={ formConfig }/>
</TestApp>,
{ hooksConfig },
);
......@@ -62,7 +102,7 @@ test('sourcify method +@dark-mode +@mobile', async({ mount, page }) => {
test('multi-part files method', async({ mount, page }) => {
const component = await mount(
<TestApp>
<ContractVerificationForm/>
<ContractVerificationForm config={ formConfig }/>
</TestApp>,
{ hooksConfig },
);
......@@ -75,7 +115,7 @@ test('multi-part files method', async({ mount, page }) => {
test('vyper contract method', async({ mount, page }) => {
const component = await mount(
<TestApp>
<ContractVerificationForm/>
<ContractVerificationForm config={ formConfig }/>
</TestApp>,
{ hooksConfig },
);
......
import { Button, chakra } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
import type { SubmitHandler } from 'react-hook-form';
import { useForm, FormProvider } from 'react-hook-form';
import type { FormFields, VerificationMethod } from './types';
import type { FormFields } from './types';
import type { SmartContractVerificationMethod, SmartContractVerificationConfig } from 'types/api/contract';
import delay from 'lib/delay';
import ContractVerificationFieldMethod, { VERIFICATION_METHODS } from './fields/ContractVerificationFieldMethod';
import ContractVerificationFieldMethod from './fields/ContractVerificationFieldMethod';
import ContractVerificationFlattenSourceCode from './methods/ContractVerificationFlattenSourceCode';
import ContractVerificationMultiPartFile from './methods/ContractVerificationMultiPartFile';
import ContractVerificationSourcify from './methods/ContractVerificationSourcify';
import ContractVerificationStandardInput from './methods/ContractVerificationStandardInput';
import ContractVerificationVyperContract from './methods/ContractVerificationVyperContract';
const METHODS = {
flatten_source_code: <ContractVerificationFlattenSourceCode/>,
const METHOD_COMPONENTS = {
flattened_code: <ContractVerificationFlattenSourceCode/>,
standard_input: <ContractVerificationStandardInput/>,
sourcify: <ContractVerificationSourcify/>,
multi_part_file: <ContractVerificationMultiPartFile/>,
vyper_contract: <ContractVerificationVyperContract/>,
multi_part: <ContractVerificationMultiPartFile/>,
vyper_multi_part: <ContractVerificationVyperContract/>,
};
const ContractVerificationForm = () => {
const router = useRouter();
const methodFromQuery = router.query.method?.toString() as VerificationMethod;
interface Props {
method?: SmartContractVerificationMethod;
config: SmartContractVerificationConfig;
}
const ContractVerificationForm = ({ method: methodFromQuery, config }: Props) => {
const formApi = useForm<FormFields>({
mode: 'onBlur',
defaultValues: {
method: VERIFICATION_METHODS.includes(methodFromQuery) ? methodFromQuery : undefined,
method: methodFromQuery,
},
});
const { control, handleSubmit, watch, formState } = formApi;
......@@ -42,16 +45,19 @@ const ContractVerificationForm = () => {
const method = watch('method');
const content = METHODS[method] || null;
const content = METHOD_COMPONENTS[method] || null;
return (
<FormProvider { ...formApi }>
<chakra.form
noValidate
onSubmit={ handleSubmit(onFormSubmit) }
mt={ 12 }
>
<ContractVerificationFieldMethod control={ control } isDisabled={ Boolean(method) }/>
<ContractVerificationFieldMethod
control={ control }
isDisabled={ Boolean(method) }
methods={ config.verification_options }
/>
{ content }
{ Boolean(method) && (
<Button
......
import { Code, Checkbox } from '@chakra-ui/react';
import { Code } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query';
import React from 'react';
import type { ControllerRenderProps } from 'react-hook-form';
import { useFormContext, Controller } from 'react-hook-form';
import type { FormFields } from '../types';
import type { SmartContractVerificationConfig } from 'types/api/contract';
import { getResourceKey } from 'lib/api/useApiQuery';
import useIsMobile from 'lib/hooks/useIsMobile';
import FancySelect from 'ui/shared/FancySelect/FancySelect';
import ContractVerificationFormRow from '../ContractVerificationFormRow';
const COMPILERS = [
'v0.8.17+commit.8df45f5f',
'v0.8.16+commit.07a7930e',
'v0.8.15+commit.e14f2714',
];
const COMPILERS_NIGHTLY = [
'v0.8.18-nightly.2022.11.23+commit.eb2f874e',
'v0.8.17-nightly.2022.8.24+commit.22a0c46e',
'v0.8.16-nightly.2022.7.6+commit.b6f11b33',
];
interface Props {
isVyper?: boolean;
}
const ContractVerificationFieldCompiler = ({ isVyper }: Props) => {
const [ isNightly, setIsNightly ] = React.useState(false);
const { formState, control, getValues, resetField } = useFormContext<FormFields>();
const { formState, control } = useFormContext<FormFields>();
const isMobile = useIsMobile();
const queryClient = useQueryClient();
const config = queryClient.getQueryData<SmartContractVerificationConfig>(getResourceKey('contract_verification_config'));
const options = React.useMemo(() => (
[
...COMPILERS, ...(isNightly ? COMPILERS_NIGHTLY : []),
].map((option) => ({ label: option, value: option }))
), [ isNightly ]);
const handleCheckboxChange = React.useCallback(() => {
if (isNightly) {
const field = getValues('compiler');
field.value.includes('nightly') && resetField('compiler', { defaultValue: null });
}
setIsNightly(prev => !prev);
}, [ getValues, isNightly, resetField ]);
(isVyper ? config?.vyper_compiler_versions : config?.solidity_compiler_versions)?.map((option) => ({ label: option, value: option })) || []
), [ config?.solidity_compiler_versions, config?.vyper_compiler_versions, isVyper ]);
const renderControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'compiler'>}) => {
const error = 'compiler' in formState.errors ? formState.errors.compiler : undefined;
......@@ -63,24 +45,12 @@ const ContractVerificationFieldCompiler = ({ isVyper }: Props) => {
return (
<ContractVerificationFormRow>
<>
<Controller
name="compiler"
control={ control }
render={ renderControl }
rules={{ required: true }}
/>
{ !isVyper && (
<Checkbox
size="lg"
mt={ 3 }
onChange={ handleCheckboxChange }
isDisabled={ formState.isSubmitting }
>
Include nightly builds
</Checkbox>
) }
</>
<Controller
name="compiler"
control={ control }
render={ renderControl }
rules={{ required: true }}
/>
{ isVyper ? null : (
<>
<span>The compiler version is specified in </span>
......
import { Link } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query';
import React from 'react';
import type { ControllerRenderProps } from 'react-hook-form';
import { useFormContext, Controller } from 'react-hook-form';
import type { FormFields } from '../types';
import type { SmartContractVerificationConfig } from 'types/api/contract';
import { getResourceKey } from 'lib/api/useApiQuery';
import useIsMobile from 'lib/hooks/useIsMobile';
import FancySelect from 'ui/shared/FancySelect/FancySelect';
import ContractVerificationFormRow from '../ContractVerificationFormRow';
const VERSIONS = [
'default',
'london',
'berlin',
];
interface Props {
isVyper?: boolean;
}
const ContractVerificationFieldEvmVersion = () => {
const ContractVerificationFieldEvmVersion = ({ isVyper }: Props) => {
const { formState, control } = useFormContext<FormFields>();
const isMobile = useIsMobile();
const queryClient = useQueryClient();
const config = queryClient.getQueryData<SmartContractVerificationConfig>(getResourceKey('contract_verification_config'));
const options = React.useMemo(() => (
VERSIONS.map((option) => ({ label: option, value: option }))
), [ ]);
(isVyper ? config?.vyper_evm_versions : config?.solidity_evm_versions)?.map((option) => ({ label: option, value: option })) || []
), [ config?.solidity_evm_versions, config?.vyper_evm_versions, isVyper ]);
const renderControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'evm_version'>}) => {
const error = 'evm_version' in formState.errors ? formState.errors.evm_version : undefined;
......
......@@ -20,30 +20,24 @@ import React from 'react';
import type { ControllerRenderProps, Control } from 'react-hook-form';
import { Controller } from 'react-hook-form';
import type { FormFields, VerificationMethod } from '../types';
import type { FormFields } from '../types';
import type { SmartContractVerificationMethod, SmartContractVerificationConfig } from 'types/api/contract';
import infoIcon from 'icons/info.svg';
export const VERIFICATION_METHODS: Array<VerificationMethod> = [
'flatten_source_code',
'standard_input',
'sourcify',
'multi_part_file',
'vyper_contract',
];
interface Props {
control: Control<FormFields>;
isDisabled?: boolean;
methods: SmartContractVerificationConfig['verification_options'];
}
const ContractVerificationFieldMethod = ({ control, isDisabled }: Props) => {
const ContractVerificationFieldMethod = ({ control, isDisabled, methods }: Props) => {
const [ isPopoverOpen, setIsPopoverOpen ] = useBoolean();
const tooltipBg = useColorModeValue('gray.700', 'gray.900');
const renderItem = React.useCallback((method: VerificationMethod) => {
const renderItem = React.useCallback((method: SmartContractVerificationMethod) => {
switch (method) {
case 'flatten_source_code':
case 'flattened_code':
return 'Via flattened source code';
case 'standard_input':
return (
......@@ -91,9 +85,9 @@ const ContractVerificationFieldMethod = ({ control, isDisabled }: Props) => {
</Popover>
</>
);
case 'multi_part_file':
case 'multi_part':
return 'Via multi-part files';
case 'vyper_contract':
case 'vyper_multi_part':
return 'Vyper contract';
default:
......@@ -105,13 +99,13 @@ const ContractVerificationFieldMethod = ({ control, isDisabled }: Props) => {
return (
<RadioGroup defaultValue="add" colorScheme="blue" isDisabled={ isDisabled } { ...field }>
<Stack spacing={ 4 }>
{ VERIFICATION_METHODS.map((method) => {
{ methods.map((method) => {
return <Radio key={ method } value={ method } size="lg">{ renderItem(method) }</Radio>;
}) }
</Stack>
</RadioGroup>
);
}, [ isDisabled, renderItem ]);
}, [ isDisabled, methods, renderItem ]);
return (
<section>
......
import type { Option } from 'ui/shared/FancySelect/types';
export type VerificationMethod = 'flatten_source_code' | 'standard_input' | 'sourcify' | 'multi_part_file' | 'vyper_contract'
export interface ContractLibrary {
name: string;
address: string;
}
export interface FormFieldsFlattenSourceCode {
method: 'flatten_source_code';
method: 'flattened_code';
is_yul: boolean;
name: string;
compiler: Option;
......@@ -32,7 +30,7 @@ export interface FormFieldsSourcify {
}
export interface FormFieldsMultiPartFile {
method: 'multi_part_file';
method: 'multi_part';
compiler: Option;
evm_version: Option;
is_optimization_enabled: boolean;
......@@ -41,7 +39,7 @@ export interface FormFieldsMultiPartFile {
}
export interface FormFieldsVyperContract {
method: 'vyper_contract';
method: 'vyper_multi_part';
name: string;
compiler: Option;
code: string;
......
import type { SmartContractVerificationMethod } from 'types/api/contract';
export const SUPPORTED_VERIFICATION_METHODS: Array<SmartContractVerificationMethod> = [
'flattened_code',
'standard_input',
'sourcify',
'multi_part',
'vyper_multi_part',
];
export function isValidVerificationMethod(method?: string): method is SmartContractVerificationMethod {
return method && SUPPORTED_VERIFICATION_METHODS.includes(method as SmartContractVerificationMethod) ? true : false;
}
......@@ -2,13 +2,19 @@ import { Text } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
import type { SmartContractVerificationConfigRaw, SmartContractVerificationMethod } from 'types/api/contract';
import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/appContext';
import isBrowser from 'lib/isBrowser';
import link from 'lib/link/link';
import ContractVerificationForm from 'ui/contractVerification/ContractVerificationForm';
import { isValidVerificationMethod } from 'ui/contractVerification/utils';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import ContentLoader from 'ui/shared/ContentLoader';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle';
......@@ -21,7 +27,20 @@ const ContractVerification = () => {
const router = useRouter();
const hash = router.query.id?.toString();
const method = router.query.id?.toString();
const method = router.query.id?.toString() as SmartContractVerificationMethod | undefined;
const configQuery = useApiQuery('contract_verification_config', {
queryOptions: {
select: (data: unknown) => {
const _data = data as SmartContractVerificationConfigRaw;
return {
..._data,
verification_options: _data.verification_options.filter(isValidVerificationMethod),
};
},
enabled: Boolean(hash),
},
});
React.useEffect(() => {
if (method && hash) {
......@@ -31,6 +50,23 @@ const ContractVerification = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ ]);
const content = (() => {
if (configQuery.isError) {
return <DataFetchAlert/>;
}
if (configQuery.isLoading) {
return <ContentLoader/>;
}
return (
<ContractVerificationForm
method={ method && configQuery.data.verification_options.includes(method) ? method : undefined }
config={ configQuery.data }
/>
);
})();
return (
<Page>
<PageTitle
......@@ -39,7 +75,7 @@ const ContractVerification = () => {
backLinkLabel="Back to contract"
/>
{ hash && (
<Address>
<Address mb={ 12 }>
<AddressIcon address={{ hash, is_contract: true, implementation_name: null }} flexShrink={ 0 }/>
<Text fontFamily="heading" ml={ 2 } fontWeight={ 500 } fontSize="lg" w={{ base: '100%', lg: 'auto' }} whiteSpace="nowrap" overflow="hidden">
<HashStringShortenDynamic hash={ hash }/>
......@@ -47,7 +83,7 @@ const ContractVerification = () => {
<CopyToClipboard text={ hash }/>
</Address>
) }
<ContractVerificationForm/>
{ content }
</Page>
);
};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment