Commit 05e381ca authored by tom goriunov's avatar tom goriunov Committed by GitHub

vyper contract verification: bugfix and improvements (#896)

* change hint and enable contract name field

* fix form re-submiting

* update screenshot

* change vyper multi part method

* vyper standard input json method

* fix required error message for sources field

* pin api host

* fix locked form

* clean up
parent e07f48d0
...@@ -5,6 +5,7 @@ import React from 'react'; ...@@ -5,6 +5,7 @@ import React from 'react';
import getSeo from 'lib/next/address/getSeo'; import getSeo from 'lib/next/address/getSeo';
import ContractVerification from 'ui/pages/ContractVerification'; import ContractVerification from 'ui/pages/ContractVerification';
import Page from 'ui/shared/Page/Page';
const ContractVerificationPage: NextPage<RoutedQuery<'/address/[hash]/contract_verification'>> = const ContractVerificationPage: NextPage<RoutedQuery<'/address/[hash]/contract_verification'>> =
({ hash }: RoutedQuery<'/address/[hash]/contract_verification'>) => { ({ hash }: RoutedQuery<'/address/[hash]/contract_verification'>) => {
...@@ -16,7 +17,9 @@ const ContractVerificationPage: NextPage<RoutedQuery<'/address/[hash]/contract_v ...@@ -16,7 +17,9 @@ const ContractVerificationPage: NextPage<RoutedQuery<'/address/[hash]/contract_v
<title>{ title }</title> <title>{ title }</title>
<meta name="description" content={ description }/> <meta name="description" content={ description }/>
</Head> </Head>
<Page>
<ContractVerification/> <ContractVerification/>
</Page>
</> </>
); );
}; };
......
...@@ -120,7 +120,8 @@ export type SmartContractQueryMethodRead = SmartContractQueryMethodReadSuccess | ...@@ -120,7 +120,8 @@ export type SmartContractQueryMethodRead = SmartContractQueryMethodReadSuccess |
// VERIFICATION // VERIFICATION
export type SmartContractVerificationMethod = 'flattened-code' | 'standard-input' | 'sourcify' | 'multi-part' | 'vyper-code' | 'vyper-multi-part'; export type SmartContractVerificationMethod = 'flattened-code' | 'standard-input' | 'sourcify' | 'multi-part'
| 'vyper-code' | 'vyper-multi-part' | 'vyper-standard-input';
export interface SmartContractVerificationConfigRaw { export interface SmartContractVerificationConfigRaw {
solidity_compiler_versions: Array<string>; solidity_compiler_versions: Array<string>;
...@@ -145,6 +146,7 @@ export type SmartContractVerificationResponse = { ...@@ -145,6 +146,7 @@ export type SmartContractVerificationResponse = {
export interface SmartContractVerificationError { export interface SmartContractVerificationError {
contract_source_code?: Array<string>; contract_source_code?: Array<string>;
files?: Array<string>; files?: Array<string>;
interfaces?: Array<string>;
compiler_version?: Array<string>; compiler_version?: Array<string>;
constructor_arguments?: Array<string>; constructor_arguments?: Array<string>;
name?: Array<string>; name?: Array<string>;
......
...@@ -37,6 +37,7 @@ const formConfig: SmartContractVerificationConfig = { ...@@ -37,6 +37,7 @@ const formConfig: SmartContractVerificationConfig = {
'multi-part', 'multi-part',
'vyper-code', 'vyper-code',
'vyper-multi-part', 'vyper-multi-part',
'vyper-standard-input',
], ],
vyper_compiler_versions: [ vyper_compiler_versions: [
'v0.3.7+commit.6020b8bb', 'v0.3.7+commit.6020b8bb',
...@@ -181,3 +182,18 @@ test('vyper multi-part method', async({ mount, page }) => { ...@@ -181,3 +182,18 @@ test('vyper multi-part method', async({ mount, page }) => {
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
test('vyper vyper-standard-input method', async({ mount, page }) => {
const component = await mount(
<TestApp>
<ContractVerificationForm config={ formConfig } hash={ hash }/>
</TestApp>,
{ hooksConfig },
);
await component.getByLabel(/verification method/i).focus();
await component.getByLabel(/verification method/i).type('vyper');
await page.getByRole('button', { name: /standard json input/i }).click();
await expect(component).toHaveScreenshot();
});
...@@ -9,6 +9,7 @@ import type { SocketMessage } from 'lib/socket/types'; ...@@ -9,6 +9,7 @@ import type { SocketMessage } from 'lib/socket/types';
import type { SmartContractVerificationMethod, SmartContractVerificationConfig } from 'types/api/contract'; import type { SmartContractVerificationMethod, SmartContractVerificationConfig } from 'types/api/contract';
import useApiFetch from 'lib/api/useApiFetch'; import useApiFetch from 'lib/api/useApiFetch';
import delay from 'lib/delay';
import useToast from 'lib/hooks/useToast'; import useToast from 'lib/hooks/useToast';
import useSocketChannel from 'lib/socket/useSocketChannel'; import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage'; import useSocketMessage from 'lib/socket/useSocketMessage';
...@@ -20,6 +21,7 @@ import ContractVerificationSourcify from './methods/ContractVerificationSourcify ...@@ -20,6 +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 ContractVerificationVyperStandardInput from './methods/ContractVerificationVyperStandardInput';
import { prepareRequestBody, formatSocketErrors, getDefaultValues } from './utils'; import { prepareRequestBody, formatSocketErrors, getDefaultValues } from './utils';
interface Props { interface Props {
...@@ -59,10 +61,11 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro ...@@ -59,10 +61,11 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro
}); });
}, [ apiFetch, hash ]); }, [ apiFetch, hash ]);
const handleNewSocketMessage: SocketMessage.ContractVerification['handler'] = React.useCallback((payload) => { const handleNewSocketMessage: SocketMessage.ContractVerification['handler'] = React.useCallback(async(payload) => {
if (payload.status === 'error') { if (payload.status === 'error') {
const errors = formatSocketErrors(payload.errors); const errors = formatSocketErrors(payload.errors);
errors.forEach(([ field, error ]) => setError(field, error)); errors.filter(Boolean).forEach(([ field, error ]) => setError(field, error));
await delay(100); // have to wait a little bit, otherwise isSubmitting status will not be updated
submitPromiseResolver.current?.(null); submitPromiseResolver.current?.(null);
return; return;
} }
...@@ -121,6 +124,7 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro ...@@ -121,6 +124,7 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro
'multi-part': <ContractVerificationMultiPartFile/>, 'multi-part': <ContractVerificationMultiPartFile/>,
'vyper-code': <ContractVerificationVyperContract config={ config }/>, 'vyper-code': <ContractVerificationVyperContract config={ config }/>,
'vyper-multi-part': <ContractVerificationVyperMultiPartFile/>, 'vyper-multi-part': <ContractVerificationVyperMultiPartFile/>,
'vyper-standard-input': <ContractVerificationVyperStandardInput/>,
}; };
}, [ config ]); }, [ config ]);
const method = watch('method'); const method = watch('method');
......
...@@ -53,7 +53,15 @@ const ContractVerificationFieldEvmVersion = ({ isVyper }: Props) => { ...@@ -53,7 +53,15 @@ const ContractVerificationFieldEvmVersion = ({ isVyper }: Props) => {
/> />
<> <>
<span>The EVM version the contract is written for. If the bytecode does not match the version, we try to verify using the latest EVM version. </span> <span>The EVM version the contract is written for. If the bytecode does not match the version, we try to verify using the latest EVM version. </span>
<Link href="https://forum.poa.network/t/smart-contract-verification-evm-version-details/2318" target="_blank">EVM version details</Link> <Link
href={ isVyper ?
'https://docs.vyperlang.org/en/stable/compiling-a-contract.html#target-options' :
'https://docs.soliditylang.org/en/latest/using-the-compiler.html#target-options'
}
target="_blank"
>
EVM version details
</Link>
</> </>
</ContractVerificationFormRow> </ContractVerificationFormRow>
); );
......
...@@ -82,6 +82,19 @@ const ContractVerificationFieldMethod = ({ control, isDisabled, methods }: Props ...@@ -82,6 +82,19 @@ const ContractVerificationFieldMethod = ({ control, isDisabled, methods }: Props
return <ListItem key={ method }>Verification of Vyper contract.</ListItem>; return <ListItem key={ method }>Verification of Vyper contract.</ListItem>;
case 'vyper-multi-part': case 'vyper-multi-part':
return <ListItem key={ method }>Verification of multi-part Vyper files.</ListItem>; return <ListItem key={ method }>Verification of multi-part Vyper files.</ListItem>;
case 'vyper-standard-input':
return (
<ListItem key={ method }>
<span>Verification of Vyper contract using </span>
<Link
href="https://docs.vyperlang.org/en/stable/compiling-a-contract.html#compiler-input-and-output-json-description"
target="_blank"
>
Standard input JSON
</Link>
<span> file.</span>
</ListItem>
);
} }
}, []); }, []);
......
...@@ -16,16 +16,26 @@ import ContractVerificationFormRow from '../ContractVerificationFormRow'; ...@@ -16,16 +16,26 @@ import ContractVerificationFormRow from '../ContractVerificationFormRow';
type FileTypes = '.sol' | '.yul' | '.json' | '.vy' type FileTypes = '.sol' | '.yul' | '.json' | '.vy'
interface Props { interface Props {
name?: 'sources' | 'interfaces';
fileTypes: Array<FileTypes>; fileTypes: Array<FileTypes>;
multiple?: boolean; multiple?: boolean;
required?: boolean;
title: string; title: string;
hint: string; hint: string | React.ReactNode;
} }
const ContractVerificationFieldSources = ({ fileTypes, multiple, title, hint }: Props) => { const ContractVerificationFieldSources = ({ fileTypes, multiple, required, title, hint, name = 'sources' }: Props) => {
const { setValue, getValues, control, formState, clearErrors } = useFormContext<FormFields>(); const { setValue, getValues, control, formState, clearErrors } = useFormContext<FormFields>();
const error = 'sources' in formState.errors ? formState.errors.sources : undefined; const error = (() => {
if (name === 'sources' && 'sources' in formState.errors) {
return formState.errors.sources;
}
if (name === 'interfaces' && 'interfaces' in formState.errors) {
return formState.errors.interfaces;
}
})();
const commonError = !error?.type?.startsWith('file_') ? error : undefined; const commonError = !error?.type?.startsWith('file_') ? error : undefined;
const fileError = error?.type?.startsWith('file_') ? error : undefined; const fileError = error?.type?.startsWith('file_') ? error : undefined;
...@@ -34,12 +44,12 @@ const ContractVerificationFieldSources = ({ fileTypes, multiple, title, hint }: ...@@ -34,12 +44,12 @@ const ContractVerificationFieldSources = ({ fileTypes, multiple, title, hint }:
return; return;
} }
const value = getValues('sources').slice(); const value = getValues(name).slice();
value.splice(index, 1); value.splice(index, 1);
setValue('sources', value); setValue(name, value);
clearErrors('sources'); clearErrors(name);
}, [ getValues, clearErrors, setValue ]); }, [ getValues, name, setValue, clearErrors ]);
const renderUploadButton = React.useCallback(() => { const renderUploadButton = React.useCallback(() => {
return ( return (
...@@ -79,11 +89,24 @@ const ContractVerificationFieldSources = ({ fileTypes, multiple, title, hint }: ...@@ -79,11 +89,24 @@ const ContractVerificationFieldSources = ({ fileTypes, multiple, title, hint }:
); );
}, [ formState.isSubmitting, handleFileRemove, fileError ]); }, [ formState.isSubmitting, handleFileRemove, fileError ]);
const renderControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'sources'>}) => { const renderControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, typeof name>}) => {
const hasValue = field.value && field.value.length > 0; const hasValue = field.value && field.value.length > 0;
const errorElement = (() => {
if (commonError?.type === 'required') {
return <FieldError message="Field is required"/>;
}
if (commonError?.message) {
return <FieldError message={ commonError.message }/>;
}
return null;
})();
return ( return (
<> <>
<FileInput<FormFields, 'sources'> accept={ fileTypes.join(',') } multiple={ multiple } field={ field }> <FileInput<FormFields, typeof name> accept={ fileTypes.join(',') } multiple={ multiple } field={ field }>
{ ({ onChange }) => ( { ({ onChange }) => (
<Flex <Flex
flexDir="column" flexDir="column"
...@@ -97,12 +120,12 @@ const ContractVerificationFieldSources = ({ fileTypes, multiple, title, hint }: ...@@ -97,12 +120,12 @@ const ContractVerificationFieldSources = ({ fileTypes, multiple, title, hint }:
</Flex> </Flex>
) } ) }
</FileInput> </FileInput>
{ commonError?.message && <FieldError message={ commonError.type === 'required' ? 'Field is required' : commonError.message }/> } { errorElement }
</> </>
); );
}, [ fileTypes, multiple, commonError, formState.isSubmitting, renderFiles, renderUploadButton ]); }, [ fileTypes, multiple, commonError, formState.isSubmitting, renderFiles, renderUploadButton ]);
const validateFileType = React.useCallback(async(value: FieldPathValue<FormFields, 'sources'>): Promise<ValidateResult> => { const validateFileType = React.useCallback(async(value: FieldPathValue<FormFields, typeof name>): Promise<ValidateResult> => {
if (Array.isArray(value)) { if (Array.isArray(value)) {
const errorText = `Wrong file type. Allowed files types are ${ fileTypes.join(',') }.`; const errorText = `Wrong file type. Allowed files types are ${ fileTypes.join(',') }.`;
const errors = value.map(({ name }) => fileTypes.some((ext) => name.endsWith(ext)) ? '' : errorText); const errors = value.map(({ name }) => fileTypes.some((ext) => name.endsWith(ext)) ? '' : errorText);
...@@ -113,7 +136,7 @@ const ContractVerificationFieldSources = ({ fileTypes, multiple, title, hint }: ...@@ -113,7 +136,7 @@ const ContractVerificationFieldSources = ({ fileTypes, multiple, title, hint }:
return true; return true;
}, [ fileTypes ]); }, [ fileTypes ]);
const validateFileSize = React.useCallback(async(value: FieldPathValue<FormFields, 'sources'>): Promise<ValidateResult> => { const validateFileSize = React.useCallback(async(value: FieldPathValue<FormFields, typeof name>): Promise<ValidateResult> => {
if (Array.isArray(value)) { if (Array.isArray(value)) {
const FILE_SIZE_LIMIT = 20 * Mb; const FILE_SIZE_LIMIT = 20 * Mb;
const errors = value.map(({ size }) => size > FILE_SIZE_LIMIT ? 'File is too big. Maximum size is 20 Mb.' : ''); const errors = value.map(({ size }) => size > FILE_SIZE_LIMIT ? 'File is too big. Maximum size is 20 Mb.' : '');
...@@ -124,7 +147,7 @@ const ContractVerificationFieldSources = ({ fileTypes, multiple, title, hint }: ...@@ -124,7 +147,7 @@ const ContractVerificationFieldSources = ({ fileTypes, multiple, title, hint }:
return true; return true;
}, []); }, []);
const validateQuantity = React.useCallback(async(value: FieldPathValue<FormFields, 'sources'>): Promise<ValidateResult> => { const validateQuantity = React.useCallback(async(value: FieldPathValue<FormFields, typeof name>): Promise<ValidateResult> => {
if (!multiple && Array.isArray(value) && value.length > 1) { if (!multiple && Array.isArray(value) && value.length > 1) {
return 'You can upload only one file'; return 'You can upload only one file';
} }
...@@ -133,18 +156,18 @@ const ContractVerificationFieldSources = ({ fileTypes, multiple, title, hint }: ...@@ -133,18 +156,18 @@ const ContractVerificationFieldSources = ({ fileTypes, multiple, title, hint }:
}, [ multiple ]); }, [ multiple ]);
const rules = React.useMemo(() => ({ const rules = React.useMemo(() => ({
required: true, required,
validate: { validate: {
file_type: validateFileType, file_type: validateFileType,
file_size: validateFileSize, file_size: validateFileSize,
quantity: validateQuantity, quantity: validateQuantity,
}, },
}), [ validateFileSize, validateFileType, validateQuantity ]); }), [ validateFileSize, validateFileType, validateQuantity, required ]);
return ( return (
<ContractVerificationFormRow> <ContractVerificationFormRow>
<Controller <Controller
name="sources" name={ name }
control={ control } control={ control }
render={ renderControl } render={ renderControl }
rules={ rules } rules={ rules }
......
...@@ -18,6 +18,7 @@ const ContractVerificationMultiPartFile = () => { ...@@ -18,6 +18,7 @@ const ContractVerificationMultiPartFile = () => {
<ContractVerificationFieldSources <ContractVerificationFieldSources
fileTypes={ FILE_TYPES } fileTypes={ FILE_TYPES }
multiple multiple
required
title="Sources *.sol or *.yul files" title="Sources *.sol or *.yul files"
hint="Upload all Solidity or Yul contract source files." hint="Upload all Solidity or Yul contract source files."
/> />
......
...@@ -12,6 +12,7 @@ const ContractVerificationSourcify = () => { ...@@ -12,6 +12,7 @@ const ContractVerificationSourcify = () => {
<ContractVerificationFieldSources <ContractVerificationFieldSources
fileTypes={ FILE_TYPES } fileTypes={ FILE_TYPES }
multiple multiple
required
title="Sources and Metadata JSON" title="Sources and Metadata JSON"
hint="Upload all Solidity contract source files and JSON metadata file(s) created during contract compilation." hint="Upload all Solidity contract source files and JSON metadata file(s) created during contract compilation."
/> />
......
...@@ -19,6 +19,7 @@ const ContractVerificationStandardInput = ({ config }: { config: SmartContractVe ...@@ -19,6 +19,7 @@ const ContractVerificationStandardInput = ({ config }: { config: SmartContractVe
fileTypes={ FILE_TYPES } fileTypes={ FILE_TYPES }
title="Standard Input JSON" title="Standard Input JSON"
hint="Upload the standard input JSON file created during contract compilation." hint="Upload the standard input JSON file created during contract compilation."
required
/> />
{ !config?.is_rust_verifier_microservice_enabled && <ContractVerificationFieldAutodetectArgs/> } { !config?.is_rust_verifier_microservice_enabled && <ContractVerificationFieldAutodetectArgs/> }
</ContractVerificationMethod> </ContractVerificationMethod>
......
...@@ -12,7 +12,7 @@ import ContractVerificationFieldName from '../fields/ContractVerificationFieldNa ...@@ -12,7 +12,7 @@ import ContractVerificationFieldName from '../fields/ContractVerificationFieldNa
const ContractVerificationVyperContract = ({ config }: { config: SmartContractVerificationConfig }) => { const ContractVerificationVyperContract = ({ config }: { config: SmartContractVerificationConfig }) => {
return ( return (
<ContractVerificationMethod title="Contract verification via Vyper (contract)"> <ContractVerificationMethod title="Contract verification via Vyper (contract)">
<ContractVerificationFieldName hint="Must match the name specified in the code." isReadOnly/> <ContractVerificationFieldName hint="The contract name is the name assigned to the verified contract in Blockscout."/>
<ContractVerificationFieldCompiler isVyper/> <ContractVerificationFieldCompiler isVyper/>
{ config?.is_rust_verifier_microservice_enabled && <ContractVerificationFieldEvmVersion isVyper/> } { config?.is_rust_verifier_microservice_enabled && <ContractVerificationFieldEvmVersion isVyper/> }
<ContractVerificationFieldCode isVyper/> <ContractVerificationFieldCode isVyper/>
......
import { Link } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import ContractVerificationMethod from '../ContractVerificationMethod'; import ContractVerificationMethod from '../ContractVerificationMethod';
...@@ -5,18 +6,39 @@ import ContractVerificationFieldCompiler from '../fields/ContractVerificationFie ...@@ -5,18 +6,39 @@ import ContractVerificationFieldCompiler from '../fields/ContractVerificationFie
import ContractVerificationFieldEvmVersion from '../fields/ContractVerificationFieldEvmVersion'; import ContractVerificationFieldEvmVersion from '../fields/ContractVerificationFieldEvmVersion';
import ContractVerificationFieldSources from '../fields/ContractVerificationFieldSources'; import ContractVerificationFieldSources from '../fields/ContractVerificationFieldSources';
const FILE_TYPES = [ '.vy' as const ]; const MAIN_SOURCES_TYPES = [ '.vy' as const ];
const INTERFACE_TYPES = [ '.vy' as const, '.json' as const ];
const ContractVerificationVyperMultiPartFile = () => { const ContractVerificationVyperMultiPartFile = () => {
const interfacesHint = (
<>
<span>Add any </span>
<Link href="https://docs.vyperlang.org/en/stable/interfaces.html" target="_blank">required interfaces</Link>
<span> for the main compiled contract.</span>
</>
);
return ( return (
<ContractVerificationMethod title="Contract verification via Vyper (multi-part files)"> <ContractVerificationMethod title="Contract verification via Vyper (multi-part files)">
<ContractVerificationFieldCompiler isVyper/> <ContractVerificationFieldCompiler isVyper/>
<ContractVerificationFieldEvmVersion isVyper/> <ContractVerificationFieldEvmVersion isVyper/>
<ContractVerificationFieldSources <ContractVerificationFieldSources
fileTypes={ FILE_TYPES } name="sources"
fileTypes={ MAIN_SOURCES_TYPES }
title="Upload main *.vy source"
hint={ `
Primary compiled Vyper contract.
Only add the main contract here whose bytecode has been deployed, all other files can be uploaded to the interfaces box below.
` }
required
/>
<ContractVerificationFieldSources
name="interfaces"
fileTypes={ INTERFACE_TYPES }
multiple multiple
title="Sources *.vy files" title="Interfaces (.vy or .json)"
hint="Upload all Vyper contract source files." hint={ interfacesHint }
/> />
</ContractVerificationMethod> </ContractVerificationMethod>
); );
......
import React from 'react';
import ContractVerificationMethod from '../ContractVerificationMethod';
import ContractVerificationFieldCompiler from '../fields/ContractVerificationFieldCompiler';
import ContractVerificationFieldSources from '../fields/ContractVerificationFieldSources';
const FILE_TYPES = [ '.json' as const ];
const ContractVerificationVyperStandardInput = () => {
return (
<ContractVerificationMethod title="Contract verification via Vyper (standard JSON input) ">
<ContractVerificationFieldCompiler isVyper/>
<ContractVerificationFieldSources
fileTypes={ FILE_TYPES }
title="Standard Input JSON"
hint="Upload the standard input JSON file created during contract compilation."
required
/>
</ContractVerificationMethod>
);
};
export default React.memo(ContractVerificationVyperStandardInput);
...@@ -64,7 +64,14 @@ export interface FormFieldsVyperMultiPartFile { ...@@ -64,7 +64,14 @@ export interface FormFieldsVyperMultiPartFile {
compiler: Option | null; compiler: Option | null;
evm_version: Option | null; evm_version: Option | null;
sources: Array<File>; sources: Array<File>;
interfaces: Array<File>;
}
export interface FormFieldsVyperStandardInput {
method: MethodOption;
compiler: Option | null;
sources: Array<File>;
} }
export type FormFields = FormFieldsFlattenSourceCode | FormFieldsStandardInput | FormFieldsSourcify | export type FormFields = FormFieldsFlattenSourceCode | FormFieldsStandardInput | FormFieldsSourcify |
FormFieldsMultiPartFile | FormFieldsVyperContract | FormFieldsVyperMultiPartFile; FormFieldsMultiPartFile | FormFieldsVyperContract | FormFieldsVyperMultiPartFile | FormFieldsVyperStandardInput;
...@@ -9,6 +9,7 @@ import type { ...@@ -9,6 +9,7 @@ import type {
FormFieldsStandardInput, FormFieldsStandardInput,
FormFieldsVyperContract, FormFieldsVyperContract,
FormFieldsVyperMultiPartFile, FormFieldsVyperMultiPartFile,
FormFieldsVyperStandardInput,
} from './types'; } from './types';
import type { SmartContractVerificationMethod, SmartContractVerificationError, SmartContractVerificationConfig } from 'types/api/contract'; import type { SmartContractVerificationMethod, SmartContractVerificationError, SmartContractVerificationConfig } from 'types/api/contract';
...@@ -21,6 +22,7 @@ export const SUPPORTED_VERIFICATION_METHODS: Array<SmartContractVerificationMeth ...@@ -21,6 +22,7 @@ export const SUPPORTED_VERIFICATION_METHODS: Array<SmartContractVerificationMeth
'multi-part', 'multi-part',
'vyper-code', 'vyper-code',
'vyper-multi-part', 'vyper-multi-part',
'vyper-standard-input',
]; ];
export const METHOD_LABELS: Record<SmartContractVerificationMethod, string> = { export const METHOD_LABELS: Record<SmartContractVerificationMethod, string> = {
...@@ -30,6 +32,7 @@ export const METHOD_LABELS: Record<SmartContractVerificationMethod, string> = { ...@@ -30,6 +32,7 @@ export const METHOD_LABELS: Record<SmartContractVerificationMethod, string> = {
'multi-part': 'Solidity (Multi-part files)', 'multi-part': 'Solidity (Multi-part files)',
'vyper-code': 'Vyper (Contract)', 'vyper-code': 'Vyper (Contract)',
'vyper-multi-part': 'Vyper (Multi-part files)', 'vyper-multi-part': 'Vyper (Multi-part files)',
'vyper-standard-input': 'Vyper (Standard JSON input)',
}; };
export const DEFAULT_VALUES: Record<SmartContractVerificationMethod, FormFields> = { export const DEFAULT_VALUES: Record<SmartContractVerificationMethod, FormFields> = {
...@@ -84,7 +87,7 @@ export const DEFAULT_VALUES: Record<SmartContractVerificationMethod, FormFields> ...@@ -84,7 +87,7 @@ export const DEFAULT_VALUES: Record<SmartContractVerificationMethod, FormFields>
value: 'vyper-code' as const, value: 'vyper-code' as const,
label: METHOD_LABELS['vyper-code'], label: METHOD_LABELS['vyper-code'],
}, },
name: 'Vyper_contract', name: '',
compiler: null, compiler: null,
evm_version: null, evm_version: null,
code: '', code: '',
...@@ -99,6 +102,14 @@ export const DEFAULT_VALUES: Record<SmartContractVerificationMethod, FormFields> ...@@ -99,6 +102,14 @@ export const DEFAULT_VALUES: Record<SmartContractVerificationMethod, FormFields>
evm_version: null, evm_version: null,
sources: [], sources: [],
}, },
'vyper-standard-input': {
method: {
value: 'vyper-standard-input' as const,
label: METHOD_LABELS['vyper-standard-input'],
},
compiler: null,
sources: [],
},
}; };
export function getDefaultValues(method: SmartContractVerificationMethod, config: SmartContractVerificationConfig) { export function getDefaultValues(method: SmartContractVerificationMethod, config: SmartContractVerificationConfig) {
...@@ -169,7 +180,7 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] { ...@@ -169,7 +180,7 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] {
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, 'files');
return body; return body;
} }
...@@ -177,7 +188,7 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] { ...@@ -177,7 +188,7 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] {
case 'sourcify': { case 'sourcify': {
const _data = data as FormFieldsSourcify; const _data = data as FormFieldsSourcify;
const body = new FormData(); const body = new FormData();
addFilesToFormData(body, _data.sources); addFilesToFormData(body, _data.sources, 'files');
_data.contract_index && body.set('chosen_contract_index', _data.contract_index.value); _data.contract_index && body.set('chosen_contract_index', _data.contract_index.value);
return body; return body;
...@@ -194,7 +205,7 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] { ...@@ -194,7 +205,7 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] {
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, 'files');
return body; return body;
} }
...@@ -217,7 +228,18 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] { ...@@ -217,7 +228,18 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] {
const body = new FormData(); const body = new FormData();
_data.compiler && body.set('compiler_version', _data.compiler.value); _data.compiler && body.set('compiler_version', _data.compiler.value);
_data.evm_version && body.set('evm_version', _data.evm_version.value); _data.evm_version && body.set('evm_version', _data.evm_version.value);
addFilesToFormData(body, _data.sources); addFilesToFormData(body, _data.sources, 'files');
addFilesToFormData(body, _data.interfaces, 'interfaces');
return body;
}
case 'vyper-standard-input': {
const _data = data as FormFieldsVyperStandardInput;
const body = new FormData();
_data.compiler && body.set('compiler_version', _data.compiler.value);
addFilesToFormData(body, _data.sources, 'files');
return body; return body;
} }
...@@ -239,27 +261,33 @@ function reduceLibrariesArray(libraries: Array<ContractLibrary> | undefined) { ...@@ -239,27 +261,33 @@ function reduceLibrariesArray(libraries: Array<ContractLibrary> | undefined) {
}, {}); }, {});
} }
function addFilesToFormData(body: FormData, files: Array<File> | undefined) { function addFilesToFormData(body: FormData, files: Array<File> | undefined, fieldName: 'files' | 'interfaces') {
if (!files) { if (!files) {
return; return;
} }
for (let index = 0; index < files.length; index++) { for (let index = 0; index < files.length; index++) {
const file = files[index]; const file = files[index];
body.set(`files[${ index }]`, file, file.name); body.set(`${ fieldName }[${ index }]`, file, file.name);
} }
} }
const API_ERROR_TO_FORM_FIELD: Record<keyof SmartContractVerificationError, FieldPath<FormFields>> = { const API_ERROR_TO_FORM_FIELD: Record<keyof SmartContractVerificationError, FieldPath<FormFields>> = {
contract_source_code: 'code', contract_source_code: 'code',
files: 'sources', files: 'sources',
interfaces: 'interfaces',
compiler_version: 'compiler', compiler_version: 'compiler',
constructor_arguments: 'constructor_args', constructor_arguments: 'constructor_args',
name: 'name', name: 'name',
}; };
export function formatSocketErrors(errors: SmartContractVerificationError): Array<[FieldPath<FormFields>, ErrorOption]> { export function formatSocketErrors(errors: SmartContractVerificationError): Array<[FieldPath<FormFields>, ErrorOption] | undefined> {
return Object.entries(errors).map(([ key, value ]) => { return Object.entries(errors).map(([ key, value ]) => {
return [ API_ERROR_TO_FORM_FIELD[key as keyof SmartContractVerificationError], { message: value.join(',') } ]; const _key = key as keyof SmartContractVerificationError;
if (!API_ERROR_TO_FORM_FIELD[_key]) {
return;
}
return [ API_ERROR_TO_FORM_FIELD[_key], { message: value.join(',') } ];
}); });
} }
...@@ -15,7 +15,6 @@ import ContentLoader from 'ui/shared/ContentLoader'; ...@@ -15,7 +15,6 @@ import ContentLoader from 'ui/shared/ContentLoader';
import CopyToClipboard from 'ui/shared/CopyToClipboard'; import CopyToClipboard from 'ui/shared/CopyToClipboard';
import DataFetchAlert from 'ui/shared/DataFetchAlert'; import DataFetchAlert from 'ui/shared/DataFetchAlert';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
const ContractVerification = () => { const ContractVerification = () => {
...@@ -96,7 +95,7 @@ const ContractVerification = () => { ...@@ -96,7 +95,7 @@ const ContractVerification = () => {
}, [ appProps.referrer ]); }, [ appProps.referrer ]);
return ( return (
<Page> <>
<PageTitle <PageTitle
title="New smart contract verification" title="New smart contract verification"
backLink={ backLink } backLink={ backLink }
...@@ -111,7 +110,7 @@ const ContractVerification = () => { ...@@ -111,7 +110,7 @@ const ContractVerification = () => {
</Address> </Address>
) } ) }
{ content } { 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