Commit ca46b77f authored by tom's avatar tom

method field changes

parent 27974d19
...@@ -21,7 +21,7 @@ import ContractVerificationSourcify from './methods/ContractVerificationSourcify ...@@ -21,7 +21,7 @@ import ContractVerificationSourcify from './methods/ContractVerificationSourcify
import ContractVerificationStandardInput from './methods/ContractVerificationStandardInput'; import ContractVerificationStandardInput from './methods/ContractVerificationStandardInput';
import ContractVerificationVyperContract from './methods/ContractVerificationVyperContract'; import ContractVerificationVyperContract from './methods/ContractVerificationVyperContract';
import ContractVerificationVyperMultiPartFile from './methods/ContractVerificationVyperMultiPartFile'; import ContractVerificationVyperMultiPartFile from './methods/ContractVerificationVyperMultiPartFile';
import { prepareRequestBody, formatSocketErrors } from './utils'; import { prepareRequestBody, formatSocketErrors, METHOD_LABELS } from './utils';
const METHOD_COMPONENTS = { const METHOD_COMPONENTS = {
'flattened-code': <ContractVerificationFlattenSourceCode/>, 'flattened-code': <ContractVerificationFlattenSourceCode/>,
...@@ -42,7 +42,10 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro ...@@ -42,7 +42,10 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro
const formApi = useForm<FormFields>({ const formApi = useForm<FormFields>({
mode: 'onBlur', mode: 'onBlur',
defaultValues: { defaultValues: {
method: methodFromQuery, method: methodFromQuery ? {
value: methodFromQuery,
label: METHOD_LABELS[methodFromQuery],
} : undefined,
}, },
}); });
const { control, handleSubmit, watch, formState, setError } = formApi; const { control, handleSubmit, watch, formState, setError } = formApi;
...@@ -57,7 +60,7 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro ...@@ -57,7 +60,7 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro
try { try {
await apiFetch('contract_verification_via', { await apiFetch('contract_verification_via', {
pathParams: { method: data.method, id: hash }, pathParams: { method: data.method.value, id: hash },
fetchParams: { fetchParams: {
method: 'POST', method: 'POST',
body, body,
...@@ -125,7 +128,7 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro ...@@ -125,7 +128,7 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro
}); });
const method = watch('method'); const method = watch('method');
const content = METHOD_COMPONENTS[method] || null; const content = METHOD_COMPONENTS[method?.value] || null;
return ( return (
<FormProvider { ...formApi }> <FormProvider { ...formApi }>
...@@ -135,8 +138,8 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro ...@@ -135,8 +138,8 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro
> >
<ContractVerificationFieldMethod <ContractVerificationFieldMethod
control={ control } control={ control }
isDisabled={ Boolean(method) }
methods={ config.verification_options } methods={ config.verification_options }
isDisabled={ formState.isSubmitting }
/> />
{ content } { content }
{ Boolean(method) && ( { Boolean(method) && (
......
import { import {
RadioGroup,
Radio,
Stack,
Text,
Link, Link,
Icon, Icon,
chakra, chakra,
...@@ -14,16 +10,23 @@ import { ...@@ -14,16 +10,23 @@ import {
PopoverBody, PopoverBody,
useColorModeValue, useColorModeValue,
DarkMode, DarkMode,
useBoolean, ListItem,
OrderedList,
Grid,
Box,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { ControllerRenderProps, Control } from 'react-hook-form'; import type { ControllerRenderProps, Control } from 'react-hook-form';
import { Controller } from 'react-hook-form'; import { Controller } from 'react-hook-form';
import type { FormFields } from '../types'; import type { FormFields } from '../types';
import type { SmartContractVerificationMethod, SmartContractVerificationConfig } from 'types/api/contract'; import type { SmartContractVerificationConfig } from 'types/api/contract';
import infoIcon from 'icons/info.svg'; import infoIcon from 'icons/info.svg';
import useIsMobile from 'lib/hooks/useIsMobile';
import FancySelect from 'ui/shared/FancySelect/FancySelect';
import { METHOD_LABELS } from '../utils';
interface Props { interface Props {
control: Control<FormFields>; control: Control<FormFields>;
...@@ -32,91 +35,81 @@ interface Props { ...@@ -32,91 +35,81 @@ interface Props {
} }
const ContractVerificationFieldMethod = ({ control, isDisabled, methods }: Props) => { const ContractVerificationFieldMethod = ({ control, isDisabled, methods }: Props) => {
const [ isPopoverOpen, setIsPopoverOpen ] = useBoolean();
const tooltipBg = useColorModeValue('gray.700', 'gray.900'); const tooltipBg = useColorModeValue('gray.700', 'gray.900');
const isMobile = useIsMobile();
const options = React.useMemo(() => methods.map((method) => ({
value: method,
label: METHOD_LABELS[method],
})), [ methods ]);
const renderControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'method'>}) => {
return (
<FancySelect
{ ...field }
options={ options }
size={ isMobile ? 'md' : 'lg' }
placeholder="Verification method (compiler type)"
isDisabled={ isDisabled }
isRequired
isAsync={ false }
/>
);
}, [ isDisabled, isMobile, options ]);
const renderItem = React.useCallback((method: SmartContractVerificationMethod) => { return (
switch (method) { <section>
case 'flattened-code': <Grid columnGap="30px" rowGap={{ base: 2, lg: 4 }} templateColumns={{ base: '1fr', lg: 'minmax(auto, 680px) minmax(0, 340px)' }}>
return 'Via flattened source code'; <div>
case 'standard-input': <Box mb={ 5 }>
return ( <chakra.span fontWeight={ 500 } fontSize="lg" fontFamily="heading">
<> Currently, Blockscout supports { methods.length } contract verification methods
<span>Via standard </span> </chakra.span>
<Link <Popover trigger="hover" isLazy placement={ isMobile ? 'bottom-end' : 'right-start' }>
href={ isDisabled ? undefined : 'https://docs.soliditylang.org/en/latest/using-the-compiler.html#input-description' }
target="_blank"
cursor={ isDisabled ? 'not-allowed' : 'pointer' }
>
Input JSON
</Link>
</>
);
case 'sourcify':
return (
<>
<span>Via sourcify: sources and metadata JSON file</span>
<Popover trigger="hover" isLazy isOpen={ isDisabled ? false : isPopoverOpen } onOpen={ setIsPopoverOpen.on } onClose={ setIsPopoverOpen.off }>
<PopoverTrigger> <PopoverTrigger>
<chakra.span cursor={ isDisabled ? 'not-allowed' : 'pointer' } display="inline-block" verticalAlign="middle" h="24px" ml={ 1 }> <chakra.span display="inline-block" ml={ 1 } cursor="pointer" verticalAlign="middle" h="22px">
<Icon as={ infoIcon } boxSize={ 5 } color="link" _hover={{ color: 'link_hovered' }}/> <Icon as={ infoIcon } boxSize={ 5 } color="link" _hover={{ color: 'link_hovered' }}/>
</chakra.span> </chakra.span>
</PopoverTrigger> </PopoverTrigger>
<Portal> <Portal>
<PopoverContent bgColor={ tooltipBg }> <PopoverContent bgColor={ tooltipBg } w={{ base: '300px', lg: '380px' }}>
<PopoverArrow bgColor={ tooltipBg }/> <PopoverArrow bgColor={ tooltipBg }/>
<PopoverBody color="white"> <PopoverBody color="white">
<DarkMode> <DarkMode>
<div> <span>Currently, Blockscout supports 6 methods:</span>
<span>Verification through </span> <OrderedList>
<Link href="https://sourcify.dev/" target="_blank">Sourcify</Link> <ListItem>Verification through flattened source code.</ListItem>
</div> <ListItem>
<div> <span>Verification using </span>
<span>a) if smart-contract already verified on Sourcify, it will automatically fetch the data from the </span> <Link
<Link href="https://repo.sourcify.dev/" target="_blank">repo</Link> href="https://docs.soliditylang.org/en/latest/using-the-compiler.html#input-description"
</div> target="_blank"
<div> >
b) otherwise you will be asked to upload source files and JSON metadata file(s). Standard input JSON
</div> </Link>
<span> file.</span>
</ListItem>
<ListItem>
Verification through <Link href="https://sourcify.dev/" target="_blank">Sourcify</Link>.
</ListItem>
<ListItem>Verification of multi-part Solidity files.</ListItem>
<ListItem>Verification of Vyper contract.</ListItem>
<ListItem>Verification of multi-part Vyper files.</ListItem>
</OrderedList>
</DarkMode> </DarkMode>
</PopoverBody> </PopoverBody>
</PopoverContent> </PopoverContent>
</Portal> </Portal>
</Popover> </Popover>
</> </Box>
); <Controller
case 'multi-part': name="method"
return 'Via multi-part files'; control={ control }
case 'vyper-code': render={ renderControl }
return 'Vyper contract'; rules={{ required: true }}
case 'vyper-multi-part': />
return 'Via multi-part Vyper files'; </div>
</Grid>
default:
break;
}
}, [ isDisabled, isPopoverOpen, setIsPopoverOpen.off, setIsPopoverOpen.on, tooltipBg ]);
const renderRadioGroup = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'method'>}) => {
return (
<RadioGroup defaultValue="add" colorScheme="blue" isDisabled={ isDisabled } isFocusable={ !isDisabled } { ...field } >
<Stack spacing={ 4 }>
{ methods.map((method) => {
return <Radio key={ method } value={ method } size="lg">{ renderItem(method) }</Radio>;
}) }
</Stack>
</RadioGroup>
);
}, [ isDisabled, methods, renderItem ]);
return (
<section>
<Text variant="secondary" fontSize="sm" mb={ 5 }>Smart-contract verification method</Text>
<Controller
name="method"
control={ control }
render={ renderRadioGroup }
/>
</section> </section>
); );
}; };
......
import type { SmartContractVerificationMethod } from 'types/api/contract';
import type { Option } from 'ui/shared/FancySelect/types'; import type { Option } from 'ui/shared/FancySelect/types';
export interface ContractLibrary { export interface ContractLibrary {
name: string; name: string;
address: string; address: string;
} }
interface MethodOption {
label: string;
value: SmartContractVerificationMethod;
}
export interface FormFieldsFlattenSourceCode { export interface FormFieldsFlattenSourceCode {
method: 'flattened-code'; method: MethodOption;
is_yul: boolean; is_yul: boolean;
name: string; name: string;
compiler: Option; compiler: Option;
...@@ -19,7 +26,7 @@ export interface FormFieldsFlattenSourceCode { ...@@ -19,7 +26,7 @@ export interface FormFieldsFlattenSourceCode {
} }
export interface FormFieldsStandardInput { export interface FormFieldsStandardInput {
method: 'standard-input'; method: MethodOption;
name: string; name: string;
compiler: Option; compiler: Option;
sources: Array<File>; sources: Array<File>;
...@@ -28,12 +35,12 @@ export interface FormFieldsStandardInput { ...@@ -28,12 +35,12 @@ export interface FormFieldsStandardInput {
} }
export interface FormFieldsSourcify { export interface FormFieldsSourcify {
method: 'sourcify'; method: MethodOption;
sources: Array<File>; sources: Array<File>;
} }
export interface FormFieldsMultiPartFile { export interface FormFieldsMultiPartFile {
method: 'multi-part'; method: MethodOption;
compiler: Option; compiler: Option;
evm_version: Option; evm_version: Option;
is_optimization_enabled: boolean; is_optimization_enabled: boolean;
...@@ -43,7 +50,7 @@ export interface FormFieldsMultiPartFile { ...@@ -43,7 +50,7 @@ export interface FormFieldsMultiPartFile {
} }
export interface FormFieldsVyperContract { export interface FormFieldsVyperContract {
method: 'vyper-code'; method: MethodOption;
name: string; name: string;
compiler: Option; compiler: Option;
code: string; code: string;
...@@ -51,7 +58,7 @@ export interface FormFieldsVyperContract { ...@@ -51,7 +58,7 @@ export interface FormFieldsVyperContract {
} }
export interface FormFieldsVyperMultiPartFile { export interface FormFieldsVyperMultiPartFile {
method: 'vyper-multi-part'; method: MethodOption;
compiler: Option; compiler: Option;
evm_version: Option; evm_version: Option;
sources: Array<File>; sources: Array<File>;
......
import type { FieldPath, ErrorOption } from 'react-hook-form'; import type { FieldPath, ErrorOption } from 'react-hook-form';
import type { ContractLibrary, FormFields } from './types'; import type {
ContractLibrary,
FormFields,
FormFieldsFlattenSourceCode,
FormFieldsMultiPartFile,
FormFieldsSourcify,
FormFieldsStandardInput,
FormFieldsVyperContract,
FormFieldsVyperMultiPartFile,
} from './types';
import type { SmartContractVerificationMethod, SmartContractVerificationError } from 'types/api/contract'; import type { SmartContractVerificationMethod, SmartContractVerificationError } from 'types/api/contract';
import type { Params as FetchParams } from 'lib/hooks/useFetch'; import type { Params as FetchParams } from 'lib/hooks/useFetch';
...@@ -14,6 +23,15 @@ export const SUPPORTED_VERIFICATION_METHODS: Array<SmartContractVerificationMeth ...@@ -14,6 +23,15 @@ export const SUPPORTED_VERIFICATION_METHODS: Array<SmartContractVerificationMeth
'vyper-multi-part', 'vyper-multi-part',
]; ];
export const METHOD_LABELS: Record<SmartContractVerificationMethod, string> = {
'flattened-code': 'Solidity (Flattened source code)',
'standard-input': 'Solidity (Standart JSON input)',
sourcify: 'Solidity (Sourcify)',
'multi-part': 'Solidity (Multi-part files)',
'vyper-code': 'Vyper (Сontract)',
'vyper-multi-part': 'Vyper (Multi-part files)',
};
export function isValidVerificationMethod(method?: string): method is SmartContractVerificationMethod { export function isValidVerificationMethod(method?: string): method is SmartContractVerificationMethod {
return method && SUPPORTED_VERIFICATION_METHODS.includes(method as SmartContractVerificationMethod) ? true : false; return method && SUPPORTED_VERIFICATION_METHODS.includes(method as SmartContractVerificationMethod) ? true : false;
} }
...@@ -34,68 +52,78 @@ export function sortVerificationMethods(methodA: SmartContractVerificationMethod ...@@ -34,68 +52,78 @@ export function sortVerificationMethods(methodA: SmartContractVerificationMethod
} }
export function prepareRequestBody(data: FormFields): FetchParams['body'] { export function prepareRequestBody(data: FormFields): FetchParams['body'] {
switch (data.method) { switch (data.method.value) {
case 'flattened-code': { case 'flattened-code': {
const _data = data as FormFieldsFlattenSourceCode;
return { return {
compiler_version: data.compiler?.value, compiler_version: _data.compiler?.value,
source_code: data.code, source_code: _data.code,
is_optimization_enabled: data.is_optimization_enabled, is_optimization_enabled: _data.is_optimization_enabled,
is_yul_contract: data.is_yul, is_yul_contract: _data.is_yul,
optimization_runs: data.optimization_runs, optimization_runs: _data.optimization_runs,
contract_name: data.name, contract_name: _data.name,
libraries: reduceLibrariesArray(data.libraries), libraries: reduceLibrariesArray(_data.libraries),
evm_version: data.evm_version?.value, evm_version: _data.evm_version?.value,
autodetect_constructor_args: data.autodetect_constructor_args, autodetect_constructor_args: _data.autodetect_constructor_args,
constructor_args: data.constructor_args, constructor_args: _data.constructor_args,
}; };
} }
case 'standard-input': { case 'standard-input': {
const _data = data as FormFieldsStandardInput;
const body = new FormData(); const body = new FormData();
body.set('compiler_version', data.compiler?.value); body.set('compiler_version', _data.compiler?.value);
body.set('contract_name', data.name); body.set('contract_name', _data.name);
body.set('autodetect_constructor_args', String(Boolean(data.autodetect_constructor_args))); body.set('autodetect_constructor_args', String(Boolean(_data.autodetect_constructor_args)));
body.set('constructor_args', data.constructor_args); body.set('constructor_args', _data.constructor_args);
addFilesToFormData(body, data.sources); addFilesToFormData(body, _data.sources);
return body; return body;
} }
case 'sourcify': { case 'sourcify': {
const _data = data as FormFieldsSourcify;
const body = new FormData(); const body = new FormData();
addFilesToFormData(body, data.sources); addFilesToFormData(body, _data.sources);
return body; return body;
} }
case 'multi-part': { case 'multi-part': {
const _data = data as FormFieldsMultiPartFile;
const body = new FormData(); const body = new FormData();
body.set('compiler_version', data.compiler?.value); body.set('compiler_version', _data.compiler?.value);
body.set('evm_version', data.evm_version?.value); body.set('evm_version', _data.evm_version?.value);
body.set('is_optimization_enabled', String(Boolean(data.is_optimization_enabled))); body.set('is_optimization_enabled', String(Boolean(_data.is_optimization_enabled)));
data.is_optimization_enabled && body.set('optimization_runs', data.optimization_runs); _data.is_optimization_enabled && body.set('optimization_runs', _data.optimization_runs);
const libraries = reduceLibrariesArray(data.libraries); const libraries = reduceLibrariesArray(_data.libraries);
libraries && body.set('libraries', JSON.stringify(libraries)); libraries && body.set('libraries', JSON.stringify(libraries));
addFilesToFormData(body, data.sources); addFilesToFormData(body, _data.sources);
return body; return body;
} }
case 'vyper-code': { case 'vyper-code': {
const _data = data as FormFieldsVyperContract;
return { return {
compiler_version: data.compiler?.value, compiler_version: _data.compiler?.value,
source_code: data.code, source_code: _data.code,
contract_name: data.name, contract_name: _data.name,
constructor_args: data.constructor_args, constructor_args: _data.constructor_args,
}; };
} }
case 'vyper-multi-part': { case 'vyper-multi-part': {
const _data = data as FormFieldsVyperMultiPartFile;
const body = new FormData(); const body = new FormData();
body.set('compiler_version', data.compiler?.value); body.set('compiler_version', _data.compiler?.value);
body.set('evm_version', data.evm_version?.value); body.set('evm_version', _data.evm_version?.value);
addFilesToFormData(body, data.sources); addFilesToFormData(body, _data.sources);
return body; return body;
} }
......
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