Commit 0684b062 authored by tom goriunov's avatar tom goriunov Committed by GitHub

SolidityScan API response schema validation (#2190)

* add schema validation to solidity scan report resource

* report invalid API schema event to prometheus
parent 7e717867
...@@ -77,6 +77,7 @@ frontend: ...@@ -77,6 +77,7 @@ frontend:
NEXT_PUBLIC_AD_BANNER_PROVIDER: slise NEXT_PUBLIC_AD_BANNER_PROVIDER: slise
NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED: true NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED: true
NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES: "['/apps']" NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES: "['/apps']"
PROMETHEUS_METRICS_ENABLED: true
envFromSecret: envFromSecret:
NEXT_PUBLIC_SENTRY_DSN: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_SENTRY_DSN NEXT_PUBLIC_SENTRY_DSN: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_SENTRY_DSN
SENTRY_CSP_REPORT_URI: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SENTRY_CSP_REPORT_URI SENTRY_CSP_REPORT_URI: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SENTRY_CSP_REPORT_URI
......
...@@ -10,11 +10,12 @@ export default function buildUrl<R extends ResourceName>( ...@@ -10,11 +10,12 @@ export default function buildUrl<R extends ResourceName>(
resourceName: R, resourceName: R,
pathParams?: ResourcePathParams<R>, pathParams?: ResourcePathParams<R>,
queryParams?: Record<string, string | Array<string> | number | boolean | null | undefined>, queryParams?: Record<string, string | Array<string> | number | boolean | null | undefined>,
noProxy?: boolean,
): string { ): string {
const resource: ApiResource = RESOURCES[resourceName]; const resource: ApiResource = RESOURCES[resourceName];
const baseUrl = isNeedProxy() ? config.app.baseUrl : (resource.endpoint || config.api.endpoint); const baseUrl = !noProxy && isNeedProxy() ? config.app.baseUrl : (resource.endpoint || config.api.endpoint);
const basePath = resource.basePath !== undefined ? resource.basePath : config.api.basePath; const basePath = resource.basePath !== undefined ? resource.basePath : config.api.basePath;
const path = isNeedProxy() ? '/node-api/proxy' + basePath + resource.path : basePath + resource.path; const path = !noProxy && isNeedProxy() ? '/node-api/proxy' + basePath + resource.path : basePath + resource.path;
const url = new URL(compile(path)(pathParams), baseUrl); const url = new URL(compile(path)(pathParams), baseUrl);
queryParams && Object.entries(queryParams).forEach(([ key, value ]) => { queryParams && Object.entries(queryParams).forEach(([ key, value ]) => {
......
...@@ -57,7 +57,6 @@ import type { BackendVersionConfig } from 'types/api/configs'; ...@@ -57,7 +57,6 @@ import type { BackendVersionConfig } from 'types/api/configs';
import type { import type {
SmartContract, SmartContract,
SmartContractVerificationConfigRaw, SmartContractVerificationConfigRaw,
SolidityscanReport,
SmartContractSecurityAudits, SmartContractSecurityAudits,
} from 'types/api/contract'; } from 'types/api/contract';
import type { VerifiedContractsResponse, VerifiedContractsFilters, VerifiedContractsCounters } from 'types/api/contracts'; import type { VerifiedContractsResponse, VerifiedContractsFilters, VerifiedContractsCounters } from 'types/api/contracts';
...@@ -480,7 +479,7 @@ export const RESOURCES = { ...@@ -480,7 +479,7 @@ export const RESOURCES = {
path: '/api/v2/smart-contracts/:hash/verification/via/:method', path: '/api/v2/smart-contracts/:hash/verification/via/:method',
pathParams: [ 'hash' as const, 'method' as const ], pathParams: [ 'hash' as const, 'method' as const ],
}, },
contract_solidityscan_report: { contract_solidity_scan_report: {
path: '/api/v2/smart-contracts/:hash/solidityscan-report', path: '/api/v2/smart-contracts/:hash/solidityscan-report',
pathParams: [ 'hash' as const ], pathParams: [ 'hash' as const ],
}, },
...@@ -1038,7 +1037,7 @@ Q extends 'quick_search' ? Array<SearchResultItem> : ...@@ -1038,7 +1037,7 @@ Q extends 'quick_search' ? Array<SearchResultItem> :
Q extends 'search' ? SearchResult : Q extends 'search' ? SearchResult :
Q extends 'search_check_redirect' ? SearchRedirectResult : Q extends 'search_check_redirect' ? SearchRedirectResult :
Q extends 'contract' ? SmartContract : Q extends 'contract' ? SmartContract :
Q extends 'contract_solidityscan_report' ? SolidityscanReport : Q extends 'contract_solidity_scan_report' ? unknown :
Q extends 'verified_contracts' ? VerifiedContractsResponse : Q extends 'verified_contracts' ? VerifiedContractsResponse :
Q extends 'verified_contracts_counters' ? VerifiedContractsCounters : Q extends 'verified_contracts_counters' ? VerifiedContractsCounters :
Q extends 'visualize_sol2uml' ? visualizer.VisualizeResponse : Q extends 'visualize_sol2uml' ? visualizer.VisualizeResponse :
......
...@@ -54,6 +54,7 @@ const OG_TYPE_DICT: Record<Route['pathname'], OGPageType> = { ...@@ -54,6 +54,7 @@ const OG_TYPE_DICT: Record<Route['pathname'], OGPageType> = {
'/login': 'Regular page', '/login': 'Regular page',
'/sprite': 'Regular page', '/sprite': 'Regular page',
'/api/metrics': 'Regular page', '/api/metrics': 'Regular page',
'/api/monitoring/invalid-api-schema': 'Regular page',
'/api/log': 'Regular page', '/api/log': 'Regular page',
'/api/media-type': 'Regular page', '/api/media-type': 'Regular page',
'/api/proxy': 'Regular page', '/api/proxy': 'Regular page',
......
...@@ -58,6 +58,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = { ...@@ -58,6 +58,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/login': DEFAULT_TEMPLATE, '/login': DEFAULT_TEMPLATE,
'/sprite': DEFAULT_TEMPLATE, '/sprite': DEFAULT_TEMPLATE,
'/api/metrics': DEFAULT_TEMPLATE, '/api/metrics': DEFAULT_TEMPLATE,
'/api/monitoring/invalid-api-schema': DEFAULT_TEMPLATE,
'/api/log': DEFAULT_TEMPLATE, '/api/log': DEFAULT_TEMPLATE,
'/api/media-type': DEFAULT_TEMPLATE, '/api/media-type': DEFAULT_TEMPLATE,
'/api/proxy': DEFAULT_TEMPLATE, '/api/proxy': DEFAULT_TEMPLATE,
......
...@@ -54,6 +54,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = { ...@@ -54,6 +54,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/login': '%network_name% login', '/login': '%network_name% login',
'/sprite': '%network_name% SVG sprite', '/sprite': '%network_name% SVG sprite',
'/api/metrics': '%network_name% node API prometheus metrics', '/api/metrics': '%network_name% node API prometheus metrics',
'/api/monitoring/invalid-api-schema': '%network_name% node API prometheus metrics',
'/api/log': '%network_name% node API request log', '/api/log': '%network_name% node API request log',
'/api/media-type': '%network_name% node API media type', '/api/media-type': '%network_name% node API media type',
'/api/proxy': '%network_name% node API proxy', '/api/proxy': '%network_name% node API proxy',
......
...@@ -52,6 +52,7 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = { ...@@ -52,6 +52,7 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = {
'/login': 'Login', '/login': 'Login',
'/sprite': 'Sprite', '/sprite': 'Sprite',
'/api/metrics': 'Node API: Prometheus metrics', '/api/metrics': 'Node API: Prometheus metrics',
'/api/monitoring/invalid-api-schema': 'Node API: Prometheus metrics',
'/api/log': 'Node API: Request log', '/api/log': 'Node API: Request log',
'/api/media-type': 'Node API: Media type', '/api/media-type': 'Node API: Media type',
'/api/proxy': 'Node API: Proxy', '/api/proxy': 'Node API: Proxy',
......
...@@ -8,6 +8,12 @@ const metrics = (() => { ...@@ -8,6 +8,12 @@ const metrics = (() => {
promClient.register.clear(); promClient.register.clear();
const invalidApiSchema = new promClient.Counter({
name: 'invalid_api_schema',
help: 'Number of invalid external API schema events',
labelNames: [ 'resource', 'url' ] as const,
});
const socialPreviewBotRequests = new promClient.Counter({ const socialPreviewBotRequests = new promClient.Counter({
name: 'social_preview_bot_requests_total', name: 'social_preview_bot_requests_total',
help: 'Number of incoming requests from social preview bots', help: 'Number of incoming requests from social preview bots',
...@@ -27,7 +33,7 @@ const metrics = (() => { ...@@ -27,7 +33,7 @@ const metrics = (() => {
buckets: [ 0.2, 0.5, 1, 3, 10 ], buckets: [ 0.2, 0.5, 1, 3, 10 ],
}); });
return { socialPreviewBotRequests, searchEngineBotRequests, apiRequestDuration }; return { invalidApiSchema, socialPreviewBotRequests, searchEngineBotRequests, apiRequestDuration };
})(); })();
export default metrics; export default metrics;
import * as v from 'valibot';
export const SolidityScanIssueSeverityDistributionSchema = v.object({
critical: v.number(),
gas: v.number(),
high: v.number(),
informational: v.number(),
low: v.number(),
medium: v.number(),
});
export const SolidityScanSchema = v.object({
scan_report: v.object({
contractname: v.string(),
scan_status: v.string(),
scan_summary: v.object({
score_v2: v.string(),
issue_severity_distribution: SolidityScanIssueSeverityDistributionSchema,
}),
scanner_reference_url: v.string(),
}),
});
export type SolidityScanReport = v.InferOutput<typeof SolidityScanSchema>;
export type SolidityScanReportSeverityDistribution = v.InferOutput<typeof SolidityScanIssueSeverityDistributionSchema>;
import React from 'react';
import * as v from 'valibot';
import buildUrl from 'lib/api/buildUrl';
import useApiQuery from 'lib/api/useApiQuery';
import { SOLIDITY_SCAN_REPORT } from 'stubs/contract';
import { SolidityScanSchema } from './schema';
interface Params {
hash: string;
}
const RESOURCE_NAME = 'contract_solidity_scan_report';
const ERROR_NAME = 'Invalid response schema';
export default function useFetchReport({ hash }: Params) {
const query = useApiQuery(RESOURCE_NAME, {
pathParams: { hash },
queryOptions: {
select: (response) => {
const parsedResponse = v.safeParse(SolidityScanSchema, response);
if (!parsedResponse.success) {
throw Error(ERROR_NAME);
}
return parsedResponse.output;
},
enabled: Boolean(hash),
placeholderData: SOLIDITY_SCAN_REPORT,
retry: 0,
},
});
const errorMessage = query.error && 'message' in query.error ? query.error.message : undefined;
React.useEffect(() => {
if (errorMessage === ERROR_NAME) {
fetch('/node-api/monitoring/invalid-api-schema', {
method: 'POST',
body: JSON.stringify({
resource: RESOURCE_NAME,
url: buildUrl(RESOURCE_NAME, { hash }, undefined, true),
}),
});
}
}, [ errorMessage, hash ]);
return query;
}
import type { SolidityscanReport } from 'types/api/contract'; import type { SolidityScanReport } from 'lib/solidityScan/schema';
export const solidityscanReportAverage: SolidityscanReport = { export const solidityscanReportAverage: SolidityScanReport = {
scan_report: { scan_report: {
contractname: 'foo', contractname: 'foo',
scan_status: 'scan_done', scan_status: 'scan_done',
...@@ -13,17 +13,13 @@ export const solidityscanReportAverage: SolidityscanReport = { ...@@ -13,17 +13,13 @@ export const solidityscanReportAverage: SolidityscanReport = {
low: 2, low: 2,
medium: 0, medium: 0,
}, },
lines_analyzed_count: 18,
scan_time_taken: 1,
score: '3.61',
score_v2: '72.22', score_v2: '72.22',
threat_score: '94.74',
}, },
scanner_reference_url: 'https://solidityscan.com/quickscan/0xc1EF7811FF2ebFB74F80ed7423f2AdAA37454be2/blockscout/eth-goerli?ref=blockscout', scanner_reference_url: 'https://solidityscan.com/quickscan/0xc1EF7811FF2ebFB74F80ed7423f2AdAA37454be2/blockscout/eth-goerli?ref=blockscout',
}, },
}; };
export const solidityscanReportGreat: SolidityscanReport = { export const solidityscanReportGreat: SolidityScanReport = {
scan_report: { scan_report: {
contractname: 'foo', contractname: 'foo',
scan_status: 'scan_done', scan_status: 'scan_done',
...@@ -36,17 +32,13 @@ export const solidityscanReportGreat: SolidityscanReport = { ...@@ -36,17 +32,13 @@ export const solidityscanReportGreat: SolidityscanReport = {
low: 0, low: 0,
medium: 0, medium: 0,
}, },
lines_analyzed_count: 18,
scan_time_taken: 1,
score: '3.61',
score_v2: '100', score_v2: '100',
threat_score: '94.74',
}, },
scanner_reference_url: 'https://solidityscan.com/quickscan/0xc1EF7811FF2ebFB74F80ed7423f2AdAA37454be2/blockscout/eth-goerli?ref=blockscout', scanner_reference_url: 'https://solidityscan.com/quickscan/0xc1EF7811FF2ebFB74F80ed7423f2AdAA37454be2/blockscout/eth-goerli?ref=blockscout',
}, },
}; };
export const solidityscanReportLow: SolidityscanReport = { export const solidityscanReportLow: SolidityScanReport = {
scan_report: { scan_report: {
contractname: 'foo', contractname: 'foo',
scan_status: 'scan_done', scan_status: 'scan_done',
...@@ -59,11 +51,7 @@ export const solidityscanReportLow: SolidityscanReport = { ...@@ -59,11 +51,7 @@ export const solidityscanReportLow: SolidityscanReport = {
low: 2, low: 2,
medium: 10, medium: 10,
}, },
lines_analyzed_count: 18,
scan_time_taken: 1,
score: '3.61',
score_v2: '22.22', score_v2: '22.22',
threat_score: '94.74',
}, },
scanner_reference_url: 'https://solidityscan.com/quickscan/0xc1EF7811FF2ebFB74F80ed7423f2AdAA37454be2/blockscout/eth-goerli?ref=blockscout', scanner_reference_url: 'https://solidityscan.com/quickscan/0xc1EF7811FF2ebFB74F80ed7423f2AdAA37454be2/blockscout/eth-goerli?ref=blockscout',
}, },
......
...@@ -21,6 +21,7 @@ declare module "nextjs-routes" { ...@@ -21,6 +21,7 @@ declare module "nextjs-routes" {
| StaticRoute<"/api/log"> | StaticRoute<"/api/log">
| StaticRoute<"/api/media-type"> | StaticRoute<"/api/media-type">
| StaticRoute<"/api/metrics"> | StaticRoute<"/api/metrics">
| StaticRoute<"/api/monitoring/invalid-api-schema">
| StaticRoute<"/api/proxy"> | StaticRoute<"/api/proxy">
| StaticRoute<"/api/sprite"> | StaticRoute<"/api/sprite">
| StaticRoute<"/api-docs"> | StaticRoute<"/api-docs">
......
import type { NextApiRequest, NextApiResponse } from 'next';
import * as v from 'valibot';
import metrics from 'lib/monitoring/metrics';
const PayloadSchema = v.object({
resource: v.string(),
url: v.string(),
});
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const payload = JSON.parse(req.body);
metrics?.invalidApiSchema.inc(v.parse(PayloadSchema, payload));
} catch (error) {
}
res.status(200).json({ status: 'ok' });
}
import type { SmartContract, SolidityscanReport } from 'types/api/contract'; import type { SmartContract } from 'types/api/contract';
import type { VerifiedContract, VerifiedContractsCounters } from 'types/api/contracts'; import type { VerifiedContract, VerifiedContractsCounters } from 'types/api/contracts';
import type { SolidityScanReport } from 'lib/solidityScan/schema';
import { ADDRESS_PARAMS } from './addressParams'; import { ADDRESS_PARAMS } from './addressParams';
export const CONTRACT_CODE_UNVERIFIED = { export const CONTRACT_CODE_UNVERIFIED = {
...@@ -78,7 +80,7 @@ export const VERIFIED_CONTRACTS_COUNTERS: VerifiedContractsCounters = { ...@@ -78,7 +80,7 @@ export const VERIFIED_CONTRACTS_COUNTERS: VerifiedContractsCounters = {
new_verified_smart_contracts_24h: '1234', new_verified_smart_contracts_24h: '1234',
}; };
export const SOLIDITYSCAN_REPORT: SolidityscanReport = { export const SOLIDITY_SCAN_REPORT: SolidityScanReport = {
scan_report: { scan_report: {
contractname: 'BullRunners', contractname: 'BullRunners',
scan_status: 'scan_done', scan_status: 'scan_done',
...@@ -91,11 +93,7 @@ export const SOLIDITYSCAN_REPORT: SolidityscanReport = { ...@@ -91,11 +93,7 @@ export const SOLIDITYSCAN_REPORT: SolidityscanReport = {
low: 2, low: 2,
medium: 0, medium: 0,
}, },
lines_analyzed_count: 18,
scan_time_taken: 1,
score: '3.61',
score_v2: '72.22', score_v2: '72.22',
threat_score: '94.74',
}, },
scanner_reference_url: 'https://solidityscan.com/quickscan/0xc1EF7811FF2ebFB74F80ed7423f2AdAA37454be2/blockscout/eth-goerli?ref=blockscout', scanner_reference_url: 'https://solidityscan.com/quickscan/0xc1EF7811FF2ebFB74F80ed7423f2AdAA37454be2/blockscout/eth-goerli?ref=blockscout',
}, },
......
...@@ -107,32 +107,6 @@ export interface SmartContractVerificationError { ...@@ -107,32 +107,6 @@ export interface SmartContractVerificationError {
name?: Array<string>; name?: Array<string>;
} }
// it's an external API proxy, we can't guarantee the responce types
export type SolidityscanReport = {
scan_report?: {
contractname?: string;
scan_status?: string;
scan_summary?: {
issue_severity_distribution?: SolidityscanReportSeverityDistribution;
lines_analyzed_count?: number;
scan_time_taken?: number;
score?: string;
score_v2?: string;
threat_score?: string;
};
scanner_reference_url?: string;
};
}
export type SolidityscanReportSeverityDistribution = {
critical?: number;
gas?: number;
high?: number;
informational?: number;
low?: number;
medium?: number;
};
type SmartContractSecurityAudit = { type SmartContractSecurityAudit = {
audit_company_name: string; audit_company_name: string;
audit_publish_date: string; audit_publish_date: string;
......
import type { SolidityscanReport, SolidityscanReportSeverityDistribution } from 'types/api/contract'; import type { SolidityScanReport, SolidityScanReportSeverityDistribution } from 'lib/solidityScan/schema';
export type MarketplaceAppPreview = { export type MarketplaceAppPreview = {
id: string; id: string;
...@@ -54,12 +54,12 @@ export type MarketplaceAppSecurityReport = { ...@@ -54,12 +54,12 @@ export type MarketplaceAppSecurityReport = {
solidityScanContractsNumber: number; solidityScanContractsNumber: number;
securityScore: number; securityScore: number;
totalIssues?: number; totalIssues?: number;
issueSeverityDistribution: SolidityscanReportSeverityDistribution; issueSeverityDistribution: SolidityScanReportSeverityDistribution;
}; };
contractsData: Array<{ contractsData: Array<{
address: string; address: string;
isVerified: boolean; isVerified: boolean;
solidityScanReport?: SolidityscanReport['scan_report'] | null; solidityScanReport?: SolidityScanReport['scan_report'] | null;
}>; }>;
} }
......
...@@ -9,7 +9,7 @@ const addressHash = 'hash'; ...@@ -9,7 +9,7 @@ const addressHash = 'hash';
test('average report +@dark-mode +@mobile', async({ render, mockApiResponse, page }) => { test('average report +@dark-mode +@mobile', async({ render, mockApiResponse, page }) => {
await mockApiResponse( await mockApiResponse(
'contract_solidityscan_report', 'contract_solidity_scan_report',
solidityscanReportMock.solidityscanReportAverage, solidityscanReportMock.solidityscanReportAverage,
{ pathParams: { hash: addressHash } }, { pathParams: { hash: addressHash } },
); );
...@@ -23,7 +23,7 @@ test('average report +@dark-mode +@mobile', async({ render, mockApiResponse, pag ...@@ -23,7 +23,7 @@ test('average report +@dark-mode +@mobile', async({ render, mockApiResponse, pag
test('great report', async({ render, mockApiResponse, page }) => { test('great report', async({ render, mockApiResponse, page }) => {
await mockApiResponse( await mockApiResponse(
'contract_solidityscan_report', 'contract_solidity_scan_report',
solidityscanReportMock.solidityscanReportGreat, solidityscanReportMock.solidityscanReportGreat,
{ pathParams: { hash: addressHash } }, { pathParams: { hash: addressHash } },
); );
...@@ -41,7 +41,7 @@ test('great report', async({ render, mockApiResponse, page }) => { ...@@ -41,7 +41,7 @@ test('great report', async({ render, mockApiResponse, page }) => {
test('low report', async({ render, mockApiResponse, page }) => { test('low report', async({ render, mockApiResponse, page }) => {
await mockApiResponse( await mockApiResponse(
'contract_solidityscan_report', 'contract_solidity_scan_report',
solidityscanReportMock.solidityscanReportLow, solidityscanReportMock.solidityscanReportLow,
{ pathParams: { hash: addressHash } }, { pathParams: { hash: addressHash } },
); );
......
...@@ -5,8 +5,7 @@ import React from 'react'; ...@@ -5,8 +5,7 @@ import React from 'react';
// Probably because of the gradient // Probably because of the gradient
// eslint-disable-next-line no-restricted-imports // eslint-disable-next-line no-restricted-imports
import solidityScanIcon from 'icons/brands/solidity_scan.svg'; import solidityScanIcon from 'icons/brands/solidity_scan.svg';
import useApiQuery from 'lib/api/useApiQuery'; import useFetchReport from 'lib/solidityScan/useFetchReport';
import { SOLIDITYSCAN_REPORT } from 'stubs/contract';
import Popover from 'ui/shared/chakra/Popover'; import Popover from 'ui/shared/chakra/Popover';
import LinkExternal from 'ui/shared/links/LinkExternal'; import LinkExternal from 'ui/shared/links/LinkExternal';
import SolidityscanReportButton from 'ui/shared/solidityscanReport/SolidityscanReportButton'; import SolidityscanReportButton from 'ui/shared/solidityscanReport/SolidityscanReportButton';
...@@ -20,16 +19,9 @@ interface Props { ...@@ -20,16 +19,9 @@ interface Props {
const SolidityscanReport = ({ hash }: Props) => { const SolidityscanReport = ({ hash }: Props) => {
const { isOpen, onToggle, onClose } = useDisclosure(); const { isOpen, onToggle, onClose } = useDisclosure();
const { data, isPlaceholderData, isError } = useApiQuery('contract_solidityscan_report', { const { data, isPlaceholderData, isError } = useFetchReport({ hash });
pathParams: { hash },
queryOptions: {
enabled: Boolean(hash),
placeholderData: SOLIDITYSCAN_REPORT,
retry: 0,
},
});
if (isError || !data?.scan_report?.scan_summary) { if (isError || !data) {
return null; return null;
} }
...@@ -67,7 +59,7 @@ const SolidityscanReport = ({ hash }: Props) => { ...@@ -67,7 +59,7 @@ const SolidityscanReport = ({ hash }: Props) => {
<SolidityscanReportDetails vulnerabilities={ vulnerabilities } vulnerabilitiesCount={ vulnerabilitiesCount }/> <SolidityscanReportDetails vulnerabilities={ vulnerabilities } vulnerabilitiesCount={ vulnerabilitiesCount }/>
</Box> </Box>
) } ) }
<LinkExternal href={ data?.scan_report.scanner_reference_url }>View full report</LinkExternal> <LinkExternal href={ data.scan_report.scanner_reference_url }>View full report</LinkExternal>
</PopoverBody> </PopoverBody>
</PopoverContent> </PopoverContent>
</Popover> </Popover>
......
...@@ -42,7 +42,7 @@ const ContractListModal = ({ onClose, onBack, type, contracts }: Props) => { ...@@ -42,7 +42,7 @@ const ContractListModal = ({ onClose, onBack, type, contracts }: Props) => {
return contracts return contracts
.filter((contract) => Boolean(contract.solidityScanReport)) .filter((contract) => Boolean(contract.solidityScanReport))
.sort((a, b) => .sort((a, b) =>
(parseFloat(b.solidityScanReport?.scan_summary?.score_v2 ?? '0')) - (parseFloat(a.solidityScanReport?.scan_summary?.score_v2 ?? '0')), (parseFloat(b.solidityScanReport?.scan_summary.score_v2 ?? '0')) - (parseFloat(a.solidityScanReport?.scan_summary.score_v2 ?? '0')),
); );
case ContractListTypes.VERIFIED: case ContractListTypes.VERIFIED:
return contracts.filter((contract) => contract.isVerified); return contracts.filter((contract) => contract.isVerified);
......
import { Box, Text, PopoverTrigger, PopoverBody, PopoverContent, useDisclosure, Icon } from '@chakra-ui/react'; import { Box, Text, PopoverTrigger, PopoverBody, PopoverContent, useDisclosure, Icon } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { SolidityscanReport } from 'types/api/contract';
import config from 'configs/app'; import config from 'configs/app';
// This icon doesn't work properly when it is in the sprite // This icon doesn't work properly when it is in the sprite
// Probably because of the gradient // Probably because of the gradient
// eslint-disable-next-line no-restricted-imports // eslint-disable-next-line no-restricted-imports
import solidityScanIcon from 'icons/brands/solidity_scan.svg'; import solidityScanIcon from 'icons/brands/solidity_scan.svg';
import * as mixpanel from 'lib/mixpanel/index'; import * as mixpanel from 'lib/mixpanel/index';
import type { SolidityScanReport } from 'lib/solidityScan/schema';
import Popover from 'ui/shared/chakra/Popover'; import Popover from 'ui/shared/chakra/Popover';
import LinkExternal from 'ui/shared/links/LinkExternal'; import LinkExternal from 'ui/shared/links/LinkExternal';
import SolidityscanReportButton from 'ui/shared/solidityscanReport/SolidityscanReportButton'; import SolidityscanReportButton from 'ui/shared/solidityscanReport/SolidityscanReportButton';
...@@ -16,7 +15,7 @@ import SolidityscanReportDetails from 'ui/shared/solidityscanReport/Solidityscan ...@@ -16,7 +15,7 @@ import SolidityscanReportDetails from 'ui/shared/solidityscanReport/Solidityscan
import SolidityscanReportScore from 'ui/shared/solidityscanReport/SolidityscanReportScore'; import SolidityscanReportScore from 'ui/shared/solidityscanReport/SolidityscanReportScore';
type Props = { type Props = {
securityReport?: SolidityscanReport['scan_report'] | null; securityReport?: SolidityScanReport['scan_report'] | null;
} }
const ContractSecurityReport = ({ securityReport }: Props) => { const ContractSecurityReport = ({ securityReport }: Props) => {
...@@ -27,11 +26,11 @@ const ContractSecurityReport = ({ securityReport }: Props) => { ...@@ -27,11 +26,11 @@ const ContractSecurityReport = ({ securityReport }: Props) => {
onToggle(); onToggle();
}, [ onToggle ]); }, [ onToggle ]);
if (!securityReport?.scan_summary?.score_v2) { if (!securityReport) {
return null; return null;
} }
const url = securityReport?.scanner_reference_url; const url = securityReport.scanner_reference_url;
const { const {
score_v2: securityScore, score_v2: securityScore,
issue_severity_distribution: issueSeverityDistribution, issue_severity_distribution: issueSeverityDistribution,
......
import { Box, Flex, Text, Grid, useColorModeValue, chakra } from '@chakra-ui/react'; import { Box, Flex, Text, Grid, useColorModeValue, chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { SolidityscanReportSeverityDistribution } from 'types/api/contract'; import type { SolidityScanReportSeverityDistribution } from 'lib/solidityScan/schema';
type DistributionItem = { type DistributionItem = {
id: keyof SolidityscanReportSeverityDistribution; id: keyof SolidityScanReportSeverityDistribution;
name: string; name: string;
color: string; color: string;
} }
...@@ -19,13 +19,13 @@ const DISTRIBUTION_ITEMS: Array<DistributionItem> = [ ...@@ -19,13 +19,13 @@ const DISTRIBUTION_ITEMS: Array<DistributionItem> = [
]; ];
interface Props { interface Props {
vulnerabilities: SolidityscanReportSeverityDistribution; vulnerabilities: SolidityScanReportSeverityDistribution;
vulnerabilitiesCount: number; vulnerabilitiesCount: number;
} }
type ItemProps = { type ItemProps = {
item: DistributionItem; item: DistributionItem;
vulnerabilities: SolidityscanReportSeverityDistribution; vulnerabilities: SolidityScanReportSeverityDistribution;
vulnerabilitiesCount: number; vulnerabilitiesCount: number;
} }
......
...@@ -15904,6 +15904,11 @@ v8-to-istanbul@^9.0.1: ...@@ -15904,6 +15904,11 @@ v8-to-istanbul@^9.0.1:
"@types/istanbul-lib-coverage" "^2.0.1" "@types/istanbul-lib-coverage" "^2.0.1"
convert-source-map "^1.6.0" convert-source-map "^1.6.0"
valibot@0.38.0:
version "0.38.0"
resolved "https://registry.yarnpkg.com/valibot/-/valibot-0.38.0.tgz#2d035d2a5bd36e8ea8b48b56d44552ace1a7616f"
integrity sha512-RCJa0fetnzp+h+KN9BdgYOgtsMAG9bfoJ9JSjIhFHobKWVWyzM3jjaeNTdpFK9tQtf3q1sguXeERJ/LcmdFE7w==
valtio@1.11.2: valtio@1.11.2:
version "1.11.2" version "1.11.2"
resolved "https://registry.yarnpkg.com/valtio/-/valtio-1.11.2.tgz#b8049c02dfe65620635d23ebae9121a741bb6530" resolved "https://registry.yarnpkg.com/valtio/-/valtio-1.11.2.tgz#b8049c02dfe65620635d23ebae9121a741bb6530"
......
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