diff --git a/mocks/contract/info.ts b/mocks/contract/info.ts index 75ddc067ef..daedb99650 100644 --- a/mocks/contract/info.ts +++ b/mocks/contract/info.ts @@ -43,7 +43,7 @@ export const verified: SmartContract = { file_path: '', additional_sources: [], verified_twin_address_hash: null, - minimal_proxy_address_hash: null, + proxy_type: null, }; export const certified: SmartContract = { @@ -85,7 +85,7 @@ export const withProxyAddress: SmartContract = { ...verified, is_verified: false, verified_twin_address_hash: '0xa62744bee8646e237441cdbfdedd3458861748a8', - minimal_proxy_address_hash: '0xa62744bee8646e237441cdbfdedd3458861748a8', + proxy_type: 'eip1967', }; export const selfDestructed: SmartContract = { @@ -133,7 +133,7 @@ export const nonVerified: SmartContract = { additional_sources: [], external_libraries: null, verified_twin_address_hash: null, - minimal_proxy_address_hash: null, + proxy_type: null, language: null, license_type: null, }; diff --git a/types/api/contract.ts b/types/api/contract.ts index a4f2d17813..f2e1ad61d0 100644 --- a/types/api/contract.ts +++ b/types/api/contract.ts @@ -19,6 +19,20 @@ export type SmartContractLicenseType = 'gnu_agpl_v3' | 'bsl_1_1'; +export type SmartContractProxyType = + | 'eip1167' + | 'eip1967' + | 'eip1822' + | 'eip930' + | 'eip2535' + | 'master_copy' + | 'basic_implementation' + | 'basic_get_implementation' + | 'comptroller' + | 'clone_with_immutable_arguments' + | 'unknown' + | null; + export interface SmartContract { deployed_bytecode: string | null; creation_bytecode: string | null; @@ -53,7 +67,7 @@ export interface SmartContract { remappings?: Array; }; verified_twin_address_hash: string | null; - minimal_proxy_address_hash: string | null; + proxy_type: SmartContractProxyType | null; language: string | null; license_type: SmartContractLicenseType | null; certified?: boolean; diff --git a/ui/address/contract/ContractCode.tsx b/ui/address/contract/ContractCode.tsx index 63642dbd2c..a75f3a37fc 100644 --- a/ui/address/contract/ContractCode.tsx +++ b/ui/address/contract/ContractCode.tsx @@ -1,4 +1,4 @@ -import { Flex, Skeleton, Button, Grid, GridItem, Alert, Link, chakra, Box, useColorModeValue } from '@chakra-ui/react'; +import { Flex, Skeleton, Button, Grid, GridItem, Alert, chakra, Box, useColorModeValue } from '@chakra-ui/react'; import type { UseQueryResult } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query'; import type { Channel } from 'phoenix'; @@ -24,6 +24,7 @@ import LinkExternal from 'ui/shared/links/LinkExternal'; import LinkInternal from 'ui/shared/links/LinkInternal'; import RawDataSnippet from 'ui/shared/RawDataSnippet'; +import ContractCodeProxyPattern from './ContractCodeProxyPattern'; import ContractSecurityAudits from './ContractSecurityAudits'; import ContractSourceCode from './ContractSourceCode'; @@ -230,7 +231,7 @@ const ContractCode = ({ addressHash, contractQuery, channel }: Props) => { Warning! Contract bytecode has been changed and does not match the verified one. Therefore, interaction with this smart contract may be risky. ) } - { !data?.is_verified && data?.verified_twin_address_hash && !data?.minimal_proxy_address_hash && ( + { !data?.is_verified && data?.verified_twin_address_hash && (!data?.proxy_type || data.proxy_type === 'unknown') && ( Contract is not verified. However, we found a verified contract with the same bytecode in Blockscout DB { page ) } - { data?.minimal_proxy_address_hash && ( - - Minimal Proxy Contract for - - . - - EIP-1167 - - minimal bytecode implementation that delegates all calls to a known address - - - ) } + { data?.proxy_type && } { data?.is_verified && ( diff --git a/ui/address/contract/ContractCodeProxyPattern.pw.tsx b/ui/address/contract/ContractCodeProxyPattern.pw.tsx new file mode 100644 index 0000000000..a2e97768c0 --- /dev/null +++ b/ui/address/contract/ContractCodeProxyPattern.pw.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +import { test, expect } from 'playwright/lib'; + +import ContractCodeProxyPattern from './ContractCodeProxyPattern'; + +test('proxy type with link +@mobile', async({ render }) => { + const component = await render(); + await expect(component).toHaveScreenshot(); +}); + +test('proxy type with link but without description', async({ render }) => { + const component = await render(); + await expect(component).toHaveScreenshot(); +}); + +test('proxy type without link', async({ render }) => { + const component = await render(); + await expect(component).toHaveScreenshot(); +}); diff --git a/ui/address/contract/ContractCodeProxyPattern.tsx b/ui/address/contract/ContractCodeProxyPattern.tsx new file mode 100644 index 0000000000..9279af0f62 --- /dev/null +++ b/ui/address/contract/ContractCodeProxyPattern.tsx @@ -0,0 +1,89 @@ +import { Alert } from '@chakra-ui/react'; +import React from 'react'; + +import type { SmartContractProxyType } from 'types/api/contract'; + +import LinkExternal from 'ui/shared/links/LinkExternal'; + +interface Props { + type: NonNullable; +} + +const PROXY_TYPES: Record, { + name: string; + link?: string; + description?: string; +}> = { + eip1167: { + name: 'EIP-1167', + link: 'https://eips.ethereum.org/EIPS/eip-1167', + description: 'Minimal proxy', + }, + eip1967: { + name: 'EIP-1967', + link: 'https://eips.ethereum.org/EIPS/eip-1967', + description: 'Proxy storage slots', + }, + eip1822: { + name: 'EIP-1822', + link: 'https://eips.ethereum.org/EIPS/eip-1822', + description: 'Universal upgradeable proxy standard (UUPS)', + }, + eip2535: { + name: 'EIP-2535', + link: 'https://eips.ethereum.org/EIPS/eip-2535', + description: 'Diamond proxy', + }, + eip930: { + name: 'ERC-930', + link: 'https://github.com/ethereum/EIPs/issues/930', + description: 'Eternal storage', + }, + clone_with_immutable_arguments: { + name: 'Clones with immutable arguments', + link: 'https://github.com/wighawag/clones-with-immutable-args', + }, + master_copy: { + name: 'GnosisSafe', + link: 'https://github.com/safe-global/safe-smart-account', + }, + comptroller: { + name: 'Compound protocol', + link: 'https://github.com/compound-finance/compound-protocol', + }, + basic_implementation: { + name: 'public implementation getter in proxy smart-contract', + }, + basic_get_implementation: { + name: 'public getImplementation getter in proxy smart-contract', + }, + unknown: { + name: 'Unknown proxy pattern', + }, +}; + +const ContractCodeProxyPattern = ({ type }: Props) => { + const proxyInfo = PROXY_TYPES[type]; + + if (!proxyInfo || type === 'unknown') { + return null; + } + + return ( + + { proxyInfo.link ? ( + <> + This proxy smart-contract is detected via { proxyInfo.name } + { proxyInfo.description && ` - ${ proxyInfo.description }` } + + ) : ( + <> + This proxy smart-contract is detected via { proxyInfo.name } + { proxyInfo.description && ` - ${ proxyInfo.description }` } + + ) } + + ); +}; + +export default React.memo(ContractCodeProxyPattern); diff --git a/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_with-proxy-address-alert-mobile-1.png b/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_with-proxy-address-alert-mobile-1.png index f60ab9af44..a73642ee31 100644 Binary files a/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_with-proxy-address-alert-mobile-1.png and b/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_with-proxy-address-alert-mobile-1.png differ diff --git a/ui/address/contract/__screenshots__/ContractCode.pw.tsx_mobile_with-proxy-address-alert-mobile-1.png b/ui/address/contract/__screenshots__/ContractCode.pw.tsx_mobile_with-proxy-address-alert-mobile-1.png index b9c761a578..5cccb20269 100644 Binary files a/ui/address/contract/__screenshots__/ContractCode.pw.tsx_mobile_with-proxy-address-alert-mobile-1.png and b/ui/address/contract/__screenshots__/ContractCode.pw.tsx_mobile_with-proxy-address-alert-mobile-1.png differ diff --git a/ui/address/contract/__screenshots__/ContractCodeProxyPattern.pw.tsx_default_proxy-type-with-link-but-without-description-1.png b/ui/address/contract/__screenshots__/ContractCodeProxyPattern.pw.tsx_default_proxy-type-with-link-but-without-description-1.png new file mode 100644 index 0000000000..fb30558ac5 Binary files /dev/null and b/ui/address/contract/__screenshots__/ContractCodeProxyPattern.pw.tsx_default_proxy-type-with-link-but-without-description-1.png differ diff --git a/ui/address/contract/__screenshots__/ContractCodeProxyPattern.pw.tsx_default_proxy-type-with-link-mobile-1.png b/ui/address/contract/__screenshots__/ContractCodeProxyPattern.pw.tsx_default_proxy-type-with-link-mobile-1.png new file mode 100644 index 0000000000..de8096a45a Binary files /dev/null and b/ui/address/contract/__screenshots__/ContractCodeProxyPattern.pw.tsx_default_proxy-type-with-link-mobile-1.png differ diff --git a/ui/address/contract/__screenshots__/ContractCodeProxyPattern.pw.tsx_default_proxy-type-without-link-1.png b/ui/address/contract/__screenshots__/ContractCodeProxyPattern.pw.tsx_default_proxy-type-without-link-1.png new file mode 100644 index 0000000000..3fe126ef5b Binary files /dev/null and b/ui/address/contract/__screenshots__/ContractCodeProxyPattern.pw.tsx_default_proxy-type-without-link-1.png differ diff --git a/ui/address/contract/__screenshots__/ContractCodeProxyPattern.pw.tsx_mobile_proxy-type-with-link-mobile-1.png b/ui/address/contract/__screenshots__/ContractCodeProxyPattern.pw.tsx_mobile_proxy-type-with-link-mobile-1.png new file mode 100644 index 0000000000..e45ff371ef Binary files /dev/null and b/ui/address/contract/__screenshots__/ContractCodeProxyPattern.pw.tsx_mobile_proxy-type-with-link-mobile-1.png differ