Commit 8c1be25f authored by tom goriunov's avatar tom goriunov Committed by GitHub

Merge pull request #1704 from blockscout/hotfix/v1.26.2

Hotfix `v1.26.2`
parents 9278adb7 12f0781e
......@@ -53,7 +53,7 @@ frontend:
NEXT_PUBLIC_FEATURED_NETWORKS: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/featured-networks/eth-goerli.json
NEXT_PUBLIC_NETWORK_LOGO: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/goerli.svg
NEXT_PUBLIC_NETWORK_ICON: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/goerli.svg
NEXT_PUBLIC_API_HOST: eth-goerli.blockscout.com
NEXT_PUBLIC_API_HOST: eth-sepolia.blockscout.com
NEXT_PUBLIC_STATS_API_HOST: https://stats-goerli.k8s-dev.blockscout.com/
NEXT_PUBLIC_VISUALIZE_API_HOST: http://visualizer-svc.visualizer-testing.svc.cluster.local/
NEXT_PUBLIC_CONTRACT_INFO_API_HOST: https://contracts-info-test.k8s-dev.blockscout.com
......
......@@ -2,8 +2,11 @@ import filetype from 'magic-bytes.js';
import hexToBytes from 'lib/hexToBytes';
import removeNonSignificantZeroBytes from './removeNonSignificantZeroBytes';
export default function guessDataType(data: string) {
const bytes = new Uint8Array(hexToBytes(data));
const filteredBytes = removeNonSignificantZeroBytes(bytes);
return filetype(bytes)[0];
return filetype(filteredBytes)[0];
}
export default function removeNonSignificantZeroBytes(bytes: Uint8Array) {
return shouldRemoveBytes(bytes) ? bytes.filter((item, index) => index % 32) : bytes;
}
// check if every 0, 32, 64, etc byte is 0 in the provided array
function shouldRemoveBytes(bytes: Uint8Array) {
let result = true;
for (let index = 0; index < bytes.length; index += 32) {
const element = bytes[index];
if (element === 0) {
continue;
} else {
result = false;
break;
}
}
return result;
}
export default function bytesToBase64(bytes: Uint8Array) {
let binary = '';
for (const byte of bytes) {
binary += String.fromCharCode(byte);
}
const base64String = btoa(binary);
return base64String;
}
import bytesToBase64 from './bytesToBase64';
import hexToBytes from './hexToBytes';
export default function hexToBase64(hex: string) {
const bytes = new Uint8Array(hexToBytes(hex));
let binary = '';
for (const byte of bytes) {
binary += String.fromCharCode(byte);
}
const base64String = btoa(binary);
return base64String;
return bytesToBase64(bytes);
}
......@@ -18,7 +18,7 @@ export default function useFormatFieldValue({ argType, argTypeMatchInt }: Params
if (argTypeMatchInt) {
const formattedString = value.replace(/\s/g, '');
return parseInt(formattedString);
return formattedString;
}
if (argType === 'bool') {
......@@ -26,11 +26,11 @@ export default function useFormatFieldValue({ argType, argTypeMatchInt }: Params
switch (formattedValue) {
case 'true': {
return true;
return 'true';
}
case 'false':{
return false;
return 'false';
}
default:
......
......@@ -18,15 +18,13 @@ export default function useValidateField({ isOptional, argType, argTypeMatchInt
return argType.match(BYTES_REGEXP);
}, [ argType ]);
// some values are formatted before they are sent to the validator
// see ./useFormatFieldValue.tsx hook
return React.useCallback((value: string | number | boolean | undefined) => {
return React.useCallback((value: string | undefined) => {
if (value === undefined || value === '') {
return isOptional ? true : 'Field is required';
}
if (argType === 'address') {
if (typeof value !== 'string' || !isAddress(value)) {
if (!isAddress(value)) {
return 'Invalid address format';
}
......@@ -41,11 +39,13 @@ export default function useValidateField({ isOptional, argType, argTypeMatchInt
}
if (argTypeMatchInt) {
if (typeof value !== 'number' || Object.is(value, NaN)) {
const formattedValue = Number(value);
if (Object.is(formattedValue, NaN)) {
return 'Invalid integer format';
}
if (value > argTypeMatchInt.max || value < argTypeMatchInt.min) {
if (formattedValue > argTypeMatchInt.max || formattedValue < argTypeMatchInt.min) {
const lowerBoundary = argTypeMatchInt.isUnsigned ? '0' : `-1 * 2 ^ ${ Number(argTypeMatchInt.power) - 1 }`;
const upperBoundary = argTypeMatchInt.isUnsigned ? `2 ^ ${ argTypeMatchInt.power } - 1` : `2 ^ ${ Number(argTypeMatchInt.power) - 1 } - 1`;
return `Value should be in range from "${ lowerBoundary }" to "${ upperBoundary }" inclusively`;
......@@ -55,7 +55,7 @@ export default function useValidateField({ isOptional, argType, argTypeMatchInt
}
if (argType === 'bool') {
if (typeof value !== 'boolean') {
if (value !== 'true' && value !== 'false') {
return 'Invalid boolean format. Allowed values: true, false';
}
}
......
......@@ -4,6 +4,7 @@ import React from 'react';
import TestApp from 'playwright/TestApp';
import BlobData from './BlobData';
import imageBlobWithZeroesBytes from './image_with_zeroes.blob';
test.use({ viewport: { width: 500, height: 300 } });
......@@ -40,3 +41,13 @@ test('image', async({ mount }) => {
await expect(component).toHaveScreenshot();
});
test('image blob with zeroes bytes', async({ mount }) => {
const component = await mount(
<TestApp>
<BlobData hash="0x01" data={ imageBlobWithZeroesBytes }/>
</TestApp>,
);
await expect(component).toHaveScreenshot();
});
......@@ -2,6 +2,8 @@ import { Flex, GridItem, Select, Skeleton, Button } from '@chakra-ui/react';
import React from 'react';
import * as blobUtils from 'lib/blob';
import removeNonSignificantZeroBytes from 'lib/blob/removeNonSignificantZeroBytes';
import bytesToBase64 from 'lib/bytesToBase64';
import downloadBlob from 'lib/downloadBlob';
import hexToBase64 from 'lib/hexToBase64';
import hexToBytes from 'lib/hexToBytes';
......@@ -49,7 +51,8 @@ const BlobData = ({ data, isLoading, hash }: Props) => {
switch (format) {
case 'Image': {
const bytes = new Uint8Array(hexToBytes(data));
return new Blob([ bytes ], { type: guessedType?.mime });
const filteredBytes = removeNonSignificantZeroBytes(bytes);
return new Blob([ filteredBytes ], { type: guessedType?.mime });
}
case 'UTF-8': {
return new Blob([ hexToUtf8(data) ], { type: guessedType?.mime ?? 'text/plain' });
......@@ -74,19 +77,22 @@ const BlobData = ({ data, isLoading, hash }: Props) => {
return <RawDataSnippet data="Not an image" showCopy={ false } isLoading={ isLoading }/>;
}
const base64 = hexToBase64(data);
const bytes = new Uint8Array(hexToBytes(data));
const filteredBytes = removeNonSignificantZeroBytes(bytes);
const base64 = bytesToBase64(filteredBytes);
const imgSrc = `data:${ guessedType.mime };base64,${ base64 }`;
return <BlobDataImage src={ imgSrc }/>;
}
case 'UTF-8':
return <RawDataSnippet data={ hexToUtf8(data) } showCopy={ false } isLoading={ isLoading }/>;
return <RawDataSnippet data={ hexToUtf8(data) } showCopy={ false } isLoading={ isLoading } contentProps={{ wordBreak: 'break-word' }}/>;
case 'Base64':
return <RawDataSnippet data={ hexToBase64(data) } showCopy={ false } isLoading={ isLoading }/>;
case 'Raw':
return <RawDataSnippet data={ data } showCopy={ false } isLoading={ isLoading }/>;
default:
return <span>fallback</span>;
return <span/>;
}
})();
......@@ -119,7 +125,7 @@ const BlobData = ({ data, isLoading, hash }: Props) => {
Download
</Button>
</Skeleton>
<CopyToClipboard text={ JSON.stringify(data) } isLoading={ isLoading }/>
<CopyToClipboard text={ data } isLoading={ isLoading }/>
</Flex>
{ content }
</GridItem>
......
This source diff could not be displayed because it is too large. You can view the blob instead.
import type { ChakraProps } from '@chakra-ui/react';
import { Box, Flex, chakra, useColorModeValue, Skeleton } from '@chakra-ui/react';
import React from 'react';
......@@ -12,9 +13,10 @@ interface Props {
textareaMaxHeight?: string;
showCopy?: boolean;
isLoading?: boolean;
contentProps?: ChakraProps;
}
const RawDataSnippet = ({ data, className, title, rightSlot, beforeSlot, textareaMaxHeight, showCopy = true, isLoading }: Props) => {
const RawDataSnippet = ({ data, className, title, rightSlot, beforeSlot, textareaMaxHeight, showCopy = true, isLoading, contentProps }: Props) => {
// see issue in theme/components/Textarea.ts
const bgColor = useColorModeValue('#f5f5f6', '#1a1b1b');
return (
......@@ -39,6 +41,7 @@ const RawDataSnippet = ({ data, className, title, rightSlot, beforeSlot, textare
overflowX="hidden"
overflowY="auto"
isLoaded={ !isLoading }
{ ...contentProps }
>
{ data }
</Skeleton>
......
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