Commit b2c51065 authored by tom goriunov's avatar tom goriunov Committed by GitHub

Merge pull request #919 from blockscout/feat/contract-code-language

feat: contract code language
parents 5f069ef3 5c087298
......@@ -10,6 +10,7 @@ export function monaco(): CspDev.DirectiveDescriptor {
'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/editor/editor.main.js',
'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/editor/editor.main.nls.js',
'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/basic-languages/solidity/solidity.js',
'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/basic-languages/elixir/elixir.js',
'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/base/worker/workerMain.js',
],
'style-src': [
......
......@@ -30,6 +30,7 @@ export const verified: Partial<SmartContract> = {
{ address_hash: '0xa62744BeE8646e237441CDbfdedD3458861748A8', name: 'Sol' },
{ address_hash: '0xa62744BeE8646e237441CDbfdedD3458861748A8', name: 'math' },
],
language: 'solidity',
};
export const withMultiplePaths: Partial<SmartContract> = {
......
......@@ -27,7 +27,6 @@ export interface SmartContract {
constructor_args: string | null;
decoded_constructor_args: Array<SmartContractDecodedConstructorArg> | null;
can_be_visualized_via_sol2uml: boolean | null;
is_vyper_contract: boolean | null;
file_path: string;
additional_sources: Array<{ file_path: string; source_code: string }>;
external_libraries: Array<SmartContractExternalLibrary> | null;
......@@ -37,6 +36,7 @@ export interface SmartContract {
};
verified_twin_address_hash: string | null;
minimal_proxy_address_hash: string | null;
language: string | null;
}
export type SmartContractDecodedConstructorArg = [
......
......@@ -22,7 +22,7 @@ export interface VerifiedContractsResponse {
export interface VerifiedContractsFilters {
q: string | undefined;
filter: 'vyper' | 'solidity' | undefined;
filter: 'vyper' | 'solidity' | 'yul' | undefined;
}
export type VerifiedContractsCounters = {
......
......@@ -23,9 +23,19 @@ function getEditorData(contractInfo: SmartContract | undefined) {
return undefined;
}
const defaultName = contractInfo.is_vyper_contract ? '/index.vy' : '/index.sol';
const extension = (() => {
switch (contractInfo.language) {
case 'vyper':
return 'vy';
case 'yul':
return 'yul';
default:
return 'sol';
}
})();
return [
{ file_path: formatFilePath(contractInfo.file_path || defaultName), source_code: contractInfo.source_code },
{ file_path: formatFilePath(contractInfo.file_path || `index.${ extension }`), source_code: contractInfo.source_code },
...(contractInfo.additional_sources || []).map((source) => ({ ...source, file_path: formatFilePath(source.file_path) })),
];
}
......@@ -74,7 +84,7 @@ const ContractSourceCode = ({ address, implementationAddress }: Props) => {
const heading = (
<Skeleton isLoaded={ !isLoading } fontWeight={ 500 }>
<span>Contract source code</span>
<Text whiteSpace="pre" as="span" variant="secondary"> ({ activeContract?.is_vyper_contract ? 'Vyper' : 'Solidity' })</Text>
<Text whiteSpace="pre" as="span" variant="secondary" textTransform="capitalize"> ({ activeContract?.language })</Text>
</Skeleton>
);
......@@ -132,11 +142,19 @@ const ContractSourceCode = ({ address, implementationAddress }: Props) => {
return (
<>
<Box display={ sourceType === 'primary' ? 'block' : 'none' }>
<CodeEditor data={ primaryEditorData } remappings={ primaryContractQuery.data?.compiler_settings?.remappings }/>
<CodeEditor
data={ primaryEditorData }
remappings={ primaryContractQuery.data?.compiler_settings?.remappings }
language={ primaryContractQuery.data?.language ?? undefined }
/>
</Box>
{ secondaryEditorData && (
<Box display={ sourceType === 'secondary' ? 'block' : 'none' }>
<CodeEditor data={ secondaryEditorData } remappings={ secondaryContractQuery.data?.compiler_settings?.remappings }/>
<CodeEditor
data={ secondaryEditorData }
remappings={ secondaryContractQuery.data?.compiler_settings?.remappings }
language={ secondaryContractQuery.data?.language ?? undefined }
/>
</Box>
) }
</>
......
......@@ -61,14 +61,10 @@ const VerifiedContracts = () => {
return;
}
if ((value === 'vyper' || value === 'solidity')) {
onFilterChange({ q: debouncedSearchTerm, filter: value });
setType(value);
return;
}
const filter = value === 'all' ? undefined : value as VerifiedContractsFilters['filter'];
onFilterChange({ q: debouncedSearchTerm, filter: undefined });
setType(undefined);
onFilterChange({ q: debouncedSearchTerm, filter });
setType(filter);
}, [ debouncedSearchTerm, onFilterChange ]);
const handleSortToggle = React.useCallback((field: SortField) => {
......
......@@ -36,9 +36,10 @@ const EDITOR_HEIGHT = 500;
interface Props {
data: Array<File>;
remappings?: Array<string>;
language?: string;
}
const CodeEditor = ({ data, remappings }: Props) => {
const CodeEditor = ({ data, remappings, language }: Props) => {
const [ instance, setInstance ] = React.useState<Monaco | undefined>();
const [ editor, setEditor ] = React.useState<monaco.editor.IStandaloneCodeEditor | undefined>();
const [ index, setIndex ] = React.useState(0);
......@@ -53,6 +54,8 @@ const CodeEditor = ({ data, remappings }: Props) => {
const editorWidth = containerRect ? containerRect.width - (isMobile ? 0 : SIDE_BAR_WIDTH) : 0;
const editorLanguage = language === 'vyper' ? 'elixir' : 'sol';
React.useEffect(() => {
instance?.editor.setTheme(colorMode === 'light' ? 'blockscout-light' : 'blockscout-dark');
}, [ colorMode, instance?.editor ]);
......@@ -69,7 +72,7 @@ const CodeEditor = ({ data, remappings }: Props) => {
const loadedModelsPaths = loadedModels.map((model) => model.uri.path);
const newModels = data.slice(1)
.filter((file) => !loadedModelsPaths.includes(file.file_path))
.map((file) => monaco.editor.createModel(file.source_code, 'sol', monaco.Uri.parse(file.file_path)));
.map((file) => monaco.editor.createModel(file.source_code, editorLanguage, monaco.Uri.parse(file.file_path)));
loadedModels.concat(newModels).forEach(addFileImportDecorations);
......@@ -185,7 +188,7 @@ const CodeEditor = ({ data, remappings }: Props) => {
return (
<Box overflow="hidden" borderRadius="md" height={ `${ EDITOR_HEIGHT }px` }>
<MonacoEditor
language="sol"
language={ editorLanguage }
path={ data[index].file_path }
defaultValue={ data[index].source_code }
options={ EDITOR_OPTIONS }
......@@ -216,7 +219,7 @@ const CodeEditor = ({ data, remappings }: Props) => {
<MonacoEditor
className="editor-container"
height={ `${ EDITOR_HEIGHT }px` }
language="sol"
language={ editorLanguage }
path={ data[index].file_path }
defaultValue={ data[index].source_code }
options={ EDITOR_OPTIONS }
......
......@@ -35,6 +35,7 @@ const VerifiedContractsFilter = ({ onChange, defaultValue, isActive }: Props) =>
<MenuItemOption value="all">All</MenuItemOption>
<MenuItemOption value="solidity">Solidity</MenuItemOption>
<MenuItemOption value="vyper">Vyper</MenuItemOption>
<MenuItemOption value="yul">Yul</MenuItemOption>
</MenuOptionGroup>
</MenuList>
</Menu>
......
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