From ae0cea24988970d0bf9d9df6540a38c7d05c6f01 Mon Sep 17 00:00:00 2001 From: artemkolodko Date: Fri, 21 Oct 2022 11:51:06 +0100 Subject: [PATCH 1/6] Impeove proxy erc20 contracts support --- src/components/ERC20_Pool.tsx | 3 +- src/hooks/ERC20_Pool.ts | 4 ++- .../ContractDetails/AbiMethodView.tsx | 3 +- .../AddressPage/ContractDetails/index.tsx | 34 ++++++++----------- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/components/ERC20_Pool.tsx b/src/components/ERC20_Pool.tsx index 8640f05..9c11b93 100644 --- a/src/components/ERC20_Pool.tsx +++ b/src/components/ERC20_Pool.tsx @@ -20,7 +20,8 @@ export function ERC20_Pool() { erc20 = erc20.map((item) => { erc20Map[item.address] = { ...item, - isBridged: isTokenBridged(item.address) + isBridged: isTokenBridged(item.address), + name: item.implementationAddress ? `${item.name} (Proxy)` : item.name }; return { [IndexedDbKeyPath]: item.address, diff --git a/src/hooks/ERC20_Pool.ts b/src/hooks/ERC20_Pool.ts index e47b0f0..3b2a8d6 100644 --- a/src/hooks/ERC20_Pool.ts +++ b/src/hooks/ERC20_Pool.ts @@ -17,7 +17,8 @@ export const useERC20Pool = singletonHook(initValue, () => { erc20.forEach(item => { erc20Map[item.address] = { ...item, - isBridged: isTokenBridged(item.address) + isBridged: isTokenBridged(item.address), + name: item.implementationAddress ? `${item.name} (Proxy)` : item.name }; }) setMode(erc20Map) @@ -55,6 +56,7 @@ export interface Erc20 { image?: string; }; isBridged: boolean; + implementationAddress: string } export type ERC20_Pool = Record; diff --git a/src/pages/AddressPage/ContractDetails/AbiMethodView.tsx b/src/pages/AddressPage/ContractDetails/AbiMethodView.tsx index 9ce0d5d..364076f 100644 --- a/src/pages/AddressPage/ContractDetails/AbiMethodView.tsx +++ b/src/pages/AddressPage/ContractDetails/AbiMethodView.tsx @@ -184,7 +184,7 @@ export const AbiMethodsView = (props: { const itemType = input.type.slice(0, input.type.indexOf('[]')); return ( - + {name} ({input.type}) @@ -296,6 +296,7 @@ export const AbiMethodsView = (props: { {abiMethod.outputs ? abiMethod.outputs.map((input, idx) => { return ( { - if (!!props.sourceCode) { + if (!!props.sourceCode || (props.implementation && props.implementationSourceCode)) { return ( abiMethod.name ? ( - {props.contracts.bytecode || ""} - + } /> @@ -248,7 +247,7 @@ const TabButton = (props: { }; export const VerifiedContractDetails = (props: { - sourceCode: ISourceCode; + sourceCode: ISourceCode | null; address: string; contracts?: AddressDetails | null; shard: number; @@ -269,7 +268,7 @@ export const VerifiedContractDetails = (props: { : (chainId === 1666700000 || chainId === 1666900000); try { - abiString = JSON.stringify(props.sourceCode.abi, null, 4); + abiString = JSON.stringify(props.sourceCode?.abi, null, 4); } catch { } return ( @@ -281,7 +280,7 @@ export const VerifiedContractDetails = (props: { onClick={() => setTab(V_TABS.CODE)} selected={tab === V_TABS.CODE} /> - {props.sourceCode.abi ? ( + {props.sourceCode?.abi ? ( <> - {tab === V_TABS.CODE ? ( + {tab === V_TABS.CODE && props.sourceCode ? ( @@ -357,8 +356,7 @@ export const VerifiedContractDetails = (props: { - + } />} {props.sourceCode.supporting?.sources @@ -368,9 +366,7 @@ export const VerifiedContractDetails = (props: { key={i} label={`Verified ${source.substring(source.lastIndexOf('/') + 1)}`} value={ - - - + } /> })} @@ -382,9 +378,7 @@ export const VerifiedContractDetails = (props: { key={i} label={`Verified ${source.substring(source.lastIndexOf('/') + 1)}`} value={ - - - + } /> })} @@ -421,7 +415,9 @@ export const VerifiedContractDetails = (props: { ) : null} - {tab === V_TABS.READ && props.sourceCode.abi ? ( + {tab === V_TABS.CODE && !props.sourceCode && props.contracts && } + + {tab === V_TABS.READ && props.sourceCode && props.sourceCode.abi ? ( ) : null} - {tab === V_TABS.WRITE && props.sourceCode.abi ? ( + {tab === V_TABS.WRITE && props.sourceCode && props.sourceCode.abi ? ( Date: Fri, 21 Oct 2022 12:20:53 +0100 Subject: [PATCH 2/6] Show read and write tabs for non-verified contract --- .../AddressPage/ContractDetails/index.tsx | 57 +++++++++++-------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/src/pages/AddressPage/ContractDetails/index.tsx b/src/pages/AddressPage/ContractDetails/index.tsx index f45f44c..fc887e0 100644 --- a/src/pages/AddressPage/ContractDetails/index.tsx +++ b/src/pages/AddressPage/ContractDetails/index.tsx @@ -8,6 +8,7 @@ import { ISourceCode } from "src/api/explorerV1"; import { AbiMethodsView } from "./AbiMethodView"; import { AbiItem } from "web3-utils"; import { Wallet } from "./ConnectWallets"; +import {Address} from "../../../components/ui"; const StyledTextArea = styled(TextArea)` padding: 0.75rem; @@ -280,20 +281,18 @@ export const VerifiedContractDetails = (props: { onClick={() => setTab(V_TABS.CODE)} selected={tab === V_TABS.CODE} /> - {props.sourceCode?.abi ? ( - <> - + setTab(V_TABS.READ)} selected={tab === V_TABS.READ} - /> - + setTab(V_TABS.WRITE)} selected={tab === V_TABS.WRITE} - /> - - ) : null} + /> + {/*{props.sourceCode.proxyAddress && props.sourceCode.proxy ? (*/} {/* <>*/} {/* } - {tab === V_TABS.READ && props.sourceCode && props.sourceCode.abi ? ( - - a.stateMutability === "view" && a.type === "function" - )} - address={props.address} - isRead={V_TABS.READ === tab} - /> - + {tab === V_TABS.READ ? ( + (props.sourceCode && props.sourceCode.abi) ? + a.stateMutability === "view" && a.type === "function" + )} + address={props.address} + isRead={V_TABS.READ === tab} + /> + : + Sorry, there are no available Contract ABI methods to read. Unable to read contract info. + ) : null} - {tab === V_TABS.WRITE && props.sourceCode && props.sourceCode.abi ? ( - + {tab === V_TABS.WRITE ? ( + (props.sourceCode && props.sourceCode.abi) ? - + : + Sorry, there are no available Contract ABI methods to write. Unable to write contract info. + ) : null} {tab === V_TABS.READ_PROXY && props.implementation?.address && props.implementationSourceCode?.abi ? ( - + + + ABI for the implementation contract at +
+ a.stateMutability === "view" && a.type === "function" @@ -459,7 +466,11 @@ export const VerifiedContractDetails = (props: { ) : null} {tab === V_TABS.WRITE_PROXY && props.implementation?.address && props.implementationSourceCode?.abi ? ( - + + + ABI for the implementation contract at +
+ Date: Mon, 24 Oct 2022 10:39:22 +0100 Subject: [PATCH 3/6] Add internal txs export to csv --- .../tabs/transactions/Transactions.tsx | 2 +- src/pages/ExportData/export-utils.ts | 31 +++- src/pages/ExportData/index.tsx | 155 ++++++++++++------ src/types/blockchain.ts | 1 + 4 files changed, 136 insertions(+), 53 deletions(-) diff --git a/src/pages/AddressPage/tabs/transactions/Transactions.tsx b/src/pages/AddressPage/tabs/transactions/Transactions.tsx index e17fb51..b1b69ab 100644 --- a/src/pages/AddressPage/tabs/transactions/Transactions.tsx +++ b/src/pages/AddressPage/tabs/transactions/Transactions.tsx @@ -305,7 +305,7 @@ export function Transactions(props: { rowDetails={props.rowDetails} showPages={totalElements > 0} /> - {['transaction', 'erc20', 'erc721', 'erc1155'].includes(props.type) && + {['transaction', 'erc20', 'erc721', 'erc1155', 'internal_transaction'].includes(props.type) && diff --git a/src/pages/ExportData/export-utils.ts b/src/pages/ExportData/export-utils.ts index 916e402..880cf11 100644 --- a/src/pages/ExportData/export-utils.ts +++ b/src/pages/ExportData/export-utils.ts @@ -1,4 +1,4 @@ -import { RelatedTransaction } from "../../types"; +import {InternalTransaction, RelatedTransaction} from "../../types"; import dayjs from "dayjs"; import Big from "big.js"; import { calculateFee, calculateFeePriceUSD } from "../../utils/fee"; @@ -62,6 +62,24 @@ const mapHrc20TxToExport = (ownerAddress: string, tx: any, erc20Map: Record { + const txDate = dayjs(tx.timestamp) + const isSender = ownerAddress === tx.from + return { + Txhash: tx.transactionHash, + Blockno: tx.blockNumber, + UnixTimestamp: txDate.unix(), + DateTime: txDate.format('YYYY-MM-DD HH:MM:ss'), + From: tx.from, + To: tx.to, + ['Value_IN(ONE)']: convertValue(isSender ? '0': tx.value), + ['Value_OUT(ONE)']: convertValue(isSender ? tx.value : '0'), + [`CurrentValue @ $${onePrice}/ONE`]: convertValue(onePrice * +tx.value), + Method: tx.input.slice(0, 10), + Type: tx.type + } +} + export interface IDownloadCsvParams { type: TRelatedTransaction address: string @@ -76,12 +94,15 @@ export const downloadCSV = (params: IDownloadCsvParams, filename: string) => { const { type, address, txs, onePrice, erc20Map, erc721Map, erc1155Map } = params const mapTx = (tx: any) => { - return type === 'transaction' - ? mapRelatedTxToExport(address, tx, onePrice) - : mapHrc20TxToExport(address, tx, erc20Map, erc721Map, erc1155Map) + if(type === 'erc20') { + return mapHrc20TxToExport(address, tx, erc20Map, erc721Map, erc1155Map) + } else if (type === 'internal_transaction') { + return mapInternalTxToExport(address, tx, onePrice) + } + return mapRelatedTxToExport(address, tx, onePrice) } - const mappedTxs = txs.map((tx) => mapTx(tx)) + const mappedTxs = txs.map(mapTx) const header = mappedTxs.filter((_, index) => index === 0).map(item => Object.keys(item)) const body = mappedTxs .map((item) => Object.values(item)) diff --git a/src/pages/ExportData/index.tsx b/src/pages/ExportData/index.tsx index dbf99b3..df2dada 100644 --- a/src/pages/ExportData/index.tsx +++ b/src/pages/ExportData/index.tsx @@ -4,7 +4,7 @@ import { Address, BaseContainer, BasePage, Button, TipContent } from "src/compon import { Heading, DateInput, Box, Spinner, Tip, Text } from "grommet"; import styled from "styled-components"; import useQuery from "../../hooks/useQuery"; -import { getRelatedTransactionsByType } from "../../api/client"; +import {getBlocks, getRelatedTransactionsByType} from "../../api/client"; import { downloadCSV } from "./export-utils"; import dayjs from "dayjs"; import { toaster } from "../../App"; @@ -35,9 +35,11 @@ const DownloadButton = styled(Button)` height: 42px; ` +const DefaultLimit = 5000 + export const ExportData = () => { const query = useQuery(); - const address = query.get('address') || ''; + const address = (query.get('address') || '').toLowerCase() const type = (query.get('type') || 'transaction') as TRelatedTransaction; const dateFormat = 'YYYY-MM-DD' @@ -63,22 +65,6 @@ export const ExportData = () => { inputProps: { width: '170px' } } - const filter = { - offset: 0, - limit: 5000, - orderBy: 'block_number', - orderDirection: 'desc', - filters: [{ - type: 'gt', - property: 'timestamp', - value: `'${dateFrom}'` - }, { - type: 'lt', - property: 'timestamp', - value: `'${dayjs(dateTo).add(1, 'day').format(dateFormat)}'` - }] - } - const showErrorNotification = () => { toaster.show({ message: () => ( @@ -93,11 +79,81 @@ export const ExportData = () => { const onDownloadClicked = async () => { try { setIsDownloading(true) + + const txsFilter: any = { + offset: 0, + limit: DefaultLimit, + orderBy: 'block_number', + orderDirection: 'desc', + filters: [] + } + + // if (type === 'transaction') { + // if (dateFrom) { + // txsFilter.filters.push({ + // type: 'gt', + // property: 'timestamp', + // value: `'${dateFrom}'` + // }) + // } + // if (dateTo) { + // txsFilter.filters.push({ + // type: 'lt', + // property: 'timestamp', + // value: `'${dayjs(dateTo).add(1, 'day').format(dateFormat)}'` + // }) + // } + // } + + // Get block numbers first, then filter internal by block number (not timestamp) + if (dateFrom) { + const blockFromFilter = { + offset: 0, + limit: 1, + orderBy: 'number', + orderDirection: 'asc', + filters: [{ + type: 'gte', + property: 'timestamp', + value: `'${dateFrom}'` + }] + } + const [blockFrom] = await getBlocks([0, blockFromFilter]); + if(blockFrom) { + txsFilter.filters.push({ + type: 'gte', + property: 'block_number', + value: blockFrom.number + }) + } + } + if (dateTo) { + const blockToFilter = { + offset: 0, + limit: 1, + orderBy: 'number', + orderDirection: 'desc', + filters: [{ + type: 'lt', + property: 'timestamp', + value: `'${dayjs(dateTo).add(1, 'day').format(dateFormat)}'` + }] + } + const [blockTo] = await getBlocks([0, blockToFilter]); + if(blockTo) { + txsFilter.filters.push({ + type: 'lte', + property: 'block_number', + value: blockTo.number + }) + } + } + const txs = await getRelatedTransactionsByType([ 0, address, type, - filter, + txsFilter, ]); const downloadParams = { type, @@ -126,42 +182,47 @@ export const ExportData = () => { } const getTxTextType = (type: TRelatedTransaction) => { - return type === 'transaction' ? 'transactions' : type + ' transactions' + if(type === 'transaction') { + return 'transactions' + } else if (type === 'internal_transaction') { + return 'internal transactions' + } + return type + ' transactions' } - return + return - Export transactions + + Download Data + ({getTxTextType(type)}) + - Export the last {filter.limit} {getTxTextType(type)} for
+ Export the last {DefaultLimit} {getTxTextType(type)} for
{type === 'transaction' && 'starting from'} - {/* TODO: support timestamp filter on backend side */} - {type === 'transaction' && - - - }> - onChangeDateFrom(value)} - /> - - -
to
- - }> - onChangeDateTo(value)} - /> - - -
- } + + + }> + onChangeDateFrom(value)} + /> + + +
to
+ + }> + onChangeDateTo(value)} + /> + + +
Date: Mon, 24 Oct 2022 11:20:36 +0100 Subject: [PATCH 4/6] Improve erc20 columns width --- src/pages/AddressPage/tabs/transactions/columns/erc20.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/AddressPage/tabs/transactions/columns/erc20.tsx b/src/pages/AddressPage/tabs/transactions/columns/erc20.tsx index 191b201..97e3c0e 100644 --- a/src/pages/AddressPage/tabs/transactions/columns/erc20.tsx +++ b/src/pages/AddressPage/tabs/transactions/columns/erc20.tsx @@ -72,7 +72,7 @@ export function getERC20Columns(id: string): ColumnConfig[] { Value @@ -103,7 +103,7 @@ export function getERC20Columns(id: string): ColumnConfig[] { Token From a7007399879093fe9b7377ed3dea895e3d82ab96 Mon Sep 17 00:00:00 2001 From: artemkolodko Date: Mon, 24 Oct 2022 12:55:28 +0100 Subject: [PATCH 5/6] Remove VerifyProxyContract page --- src/Routes.tsx | 5 - src/api/client.ts | 8 - .../VerifyProxyContract.tsx | 196 ------------------ 3 files changed, 209 deletions(-) delete mode 100644 src/pages/VerifyProxyContract/VerifyProxyContract.tsx diff --git a/src/Routes.tsx b/src/Routes.tsx index 6519617..e46d739 100644 --- a/src/Routes.tsx +++ b/src/Routes.tsx @@ -15,7 +15,6 @@ import { ExportData } from "./pages/ExportData"; import { InventoryDetailsPage } from "./pages/InventoryDetailsPage/InventoryDetailsPage"; import { ApprovalPage } from "./pages/ApprovalPage"; import { CheckHRC } from "./pages/tools/CheckHRC"; -import {VerifyProxyContract} from "./pages/VerifyProxyContract/VerifyProxyContract"; import { ChartsPage } from "./pages/ChartsPage"; import { TopStatsPage } from "./pages/TopStatsPage"; @@ -97,10 +96,6 @@ export function Routes() { - - - - diff --git a/src/api/client.ts b/src/api/client.ts index de3297d..41a891a 100644 --- a/src/api/client.ts +++ b/src/api/client.ts @@ -220,11 +220,3 @@ export async function getTokenERC1155AssetDetails(address: string, tokenID: stri // todo fix on backend return res && res[0] } - -export function getProxyImplementation(params: [ShardID, string]) { - return transport("getProxyImplementation", params) as Promise; -} - -export function assignProxyImplementation(params: [ShardID, string, string]) { - return transport("assignProxyImplementation", params) as Promise; -} diff --git a/src/pages/VerifyProxyContract/VerifyProxyContract.tsx b/src/pages/VerifyProxyContract/VerifyProxyContract.tsx deleted file mode 100644 index 337e561..0000000 --- a/src/pages/VerifyProxyContract/VerifyProxyContract.tsx +++ /dev/null @@ -1,196 +0,0 @@ -import React, { useEffect, useState } from "react"; -import {Box, Heading, Select, Spinner, Text, TextArea, TextInput, Tip} from "grommet"; -import { Alert, StatusGood } from "grommet-icons"; -import { useHistory } from "react-router-dom"; -import useQuery from "../../hooks/useQuery"; -import { - assignProxyImplementation, - getProxyImplementation -} from "../../api/client"; -import {Address, BaseContainer, BasePage, Button} from "../../components/ui"; -import styled from "styled-components"; -import {getAddress} from "../../utils"; -import {ISourceCode, loadSourceCode} from "../../api/explorerV1"; - -export const ActionButton = styled(Button)` - font-size: 14px; - padding: 8px 8px 5px 8px; - font-weight: 500; -`; - -export function VerifyProxyContract() { - const query = useQuery(); - const history = useHistory(); - const queryAddress = query.get('a') || ''; - - const [isLoading, setIsLoading] = useState(false) - const [contractAddress, setContractAddress] = useState(queryAddress) - const [implementationAddress, setImplementationAddress] = useState('') - const [implementationCode, setImplementationCode] = useState() - const [contractCode, setContractCode] = useState() - const [verificationError, setVerificationError] = useState('') - const [isVerified, setIsVerified] = useState(false) - - const enrichAddress = (address: string) => { - let a = address.trim().toLowerCase() - if(address.startsWith('one1')) { // convert one1 to 0x before send request to backend - try { - a = getAddress(address).basicHex - } catch (e) {} - } - return a - } - - const setDefaultState = () => { - setImplementationAddress('') - setVerificationError('') - setIsVerified(false) - } - - const validateAddress = (address: string) => { - if(address.length === 0) { - return '' - } else { - if(!address.startsWith('0x') && !address.startsWith('one1')) { - return 'Address should start with 0x or one1' - } - if(address.length != 42) { - return 'Address must be 42 characters long' - } - } - return '' - } - - const verifyContract = async () => { - setImplementationAddress('') - const err = validateAddress(contractAddress) - if(err) { - setVerificationError(err) - } else { - try { - setIsLoading(true) - setVerificationError('') - const formattedContractAddress = enrichAddress(contractAddress) - const implContract = await getProxyImplementation([0, formattedContractAddress]); - if(implContract) { - try { - const code = await loadSourceCode(formattedContractAddress, 0) - const implCode = await loadSourceCode(implContract.address, 0) - setContractCode(code) - setImplementationCode(implCode) - } catch (e) { - setVerificationError('Proxy or implementation contract source code is not verified. Please verify and publish the contract source before proceeding with this proxy verification.') - } - setImplementationAddress(implContract.address) - } else { - setImplementationAddress('') - setVerificationError('This contract does not look like it contains any delegatecall opcode sequence.') - } - } catch (e) { - console.error('Unable to verify contract', e) - setVerificationError('Cannot verify contract address, try again later') - } finally { - setIsLoading(false) - } - } - } - - const applyImplementationAddress = async () => { - const contract = await assignProxyImplementation([0, enrichAddress(contractAddress), implementationAddress]) - setDefaultState() - setIsVerified(true) - } - - const onChangeInput = (value: string) => { - setContractAddress(value) - setDefaultState() - } - - return ( - - - Proxy Contract Verification - - - - - - Please enter the Proxy Contract Address you would like to verify - - - ) => onChangeInput(evt.currentTarget.value)} - value={contractAddress} - disabled={false} - /> - - - - - {!implementationAddress && - - {verificationError && - - - {verificationError} - - - } - {isVerified && - - - Successfully saved. Feel free to return to the address
to view updates. - - - } - - - - Verify - - - - { - setDefaultState() - setContractAddress('') - } - }>Clear - - - - } - {implementationAddress && - - - The proxy's implementation contract is found at: - -
- - - {verificationError && - - - {verificationError} - - - } - - - Save - - Clear - - - } - - - - ) -} From dc55b36f5a14dc50fc80859b3a7e859861004c84 Mon Sep 17 00:00:00 2001 From: artemkolodko Date: Tue, 25 Oct 2022 08:16:00 +0100 Subject: [PATCH 6/6] Improve export txs filter --- src/pages/ExportData/index.tsx | 59 +++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/src/pages/ExportData/index.tsx b/src/pages/ExportData/index.tsx index df2dada..c42cf57 100644 --- a/src/pages/ExportData/index.tsx +++ b/src/pages/ExportData/index.tsx @@ -111,9 +111,9 @@ export const ExportData = () => { offset: 0, limit: 1, orderBy: 'number', - orderDirection: 'asc', + orderDirection: 'desc', filters: [{ - type: 'gte', + type: 'lte', property: 'timestamp', value: `'${dateFrom}'` }] @@ -128,16 +128,21 @@ export const ExportData = () => { } } if (dateTo) { + const diff = dayjs().diff(dayjs(dateTo), 'days') + const isCurrentDateOrNext = diff <= 0 const blockToFilter = { offset: 0, limit: 1, orderBy: 'number', orderDirection: 'desc', - filters: [{ - type: 'lt', + filters: [] as any + } + if(!isCurrentDateOrNext) { + blockToFilter.filters.push({ + type: 'lte', property: 'timestamp', value: `'${dayjs(dateTo).add(1, 'day').format(dateFormat)}'` - }] + }) } const [blockTo] = await getBlocks([0, blockToFilter]); if(blockTo) { @@ -202,27 +207,29 @@ export const ExportData = () => { Export the last {DefaultLimit} {getTxTextType(type)} for
{type === 'transaction' && 'starting from'} - - - }> - onChangeDateFrom(value)} - /> - - -
to
- - }> - onChangeDateTo(value)} - /> - - -
+ {!['erc20', 'internal_transaction'].includes(type) && + + + }> + onChangeDateFrom(value)} + /> + + +
to
+ + }> + onChangeDateTo(value)} + /> + + +
+ }