Add proxy contracts to receipts view

pull/5285/head
Manuel Wedler 5 months ago committed by Aniket
parent 8ba75251e6
commit 8147e61169
  1. 138
      apps/contract-verification/src/app/components/AccordionReceipt.tsx
  2. 18
      apps/contract-verification/src/app/types/VerificationTypes.ts
  3. 17
      apps/contract-verification/src/app/views/ReceiptsView.tsx
  4. 1
      apps/contract-verification/src/app/views/VerifyView.tsx
  5. 71
      apps/contract-verification/src/app/views/example.js

@ -1,77 +1,115 @@
import React from 'react' import React, { useMemo } from 'react'
import { SubmittedContract } from '../types/VerificationTypes' import { SubmittedContract, SubmittedProxyContract, isContract, isProxy } from '../types/VerificationTypes'
import { shortenAddress, CustomTooltip } from '@remix-ui/helper' import { shortenAddress, CustomTooltip } from '@remix-ui/helper'
import { AppContext } from '../AppContext'
interface AccordionReceiptProps { interface AccordionReceiptProps {
contract: SubmittedContract contract: SubmittedContract | SubmittedProxyContract
chainName: string
index: number index: number
} }
export const AccordionReceipt: React.FC<AccordionReceiptProps> = ({ contract, index, chainName }) => { export const AccordionReceipt: React.FC<AccordionReceiptProps> = ({ contract, index }) => {
const { chains } = React.useContext(AppContext)
const [expanded, setExpanded] = React.useState(false) const [expanded, setExpanded] = React.useState(false)
const address = isProxy(contract) ? contract.implementation.address : contract.address
const chainName = useMemo(() => {
const chainId = isProxy(contract) ? contract.implementation.chainId : contract.chainId
return chains.find((chain) => chain.chainId === parseInt(chainId))?.name ?? 'Unknown Chain'
}, [contract, chains])
const toggleAccordion = () => { const toggleAccordion = () => {
setExpanded(!expanded) setExpanded(!expanded)
} }
return ( return (
<div key={contract.address + '-' + index} className="bg-secondary p-3 accordion-item" id={contract.address + '-accordion-' + index}> <div key={address + '-' + index} className="bg-secondary p-3 accordion-item" id={address + '-accordion-' + index}>
<h3 className="accordion-header" id={`heading${index}`}> <h3 className="accordion-header" id={`heading${index}`}>
<button className="accordion-button d-flex flex-row align-items-center text-left w-100 border-0" type="button" onClick={toggleAccordion} aria-expanded={expanded} aria-controls={`collapse${index}`}> <button className="accordion-button d-flex flex-row align-items-center text-left w-100 border-0" type="button" onClick={toggleAccordion} aria-expanded={expanded} aria-controls={`collapse${index}`}>
<span className={`accordion-arrow ${expanded ? 'fa-angle-down' : 'fa-angle-right'} fa w-0`} style={{ width: '0' }}></span> <span className={`accordion-arrow ${expanded ? 'fa-angle-down' : 'fa-angle-right'} fa w-0`} style={{ width: '0' }}></span>
<span className="pl-4" style={{ fontSize: '1rem' }}> <span className="pl-4" style={{ fontSize: '1rem' }}>
<CustomTooltip tooltipText={contract.address}> <CustomTooltip tooltipText={address}>
<span>{shortenAddress(contract.address)}</span> <span>{shortenAddress(address)}</span>
</CustomTooltip>{' '} </CustomTooltip>
on {chainName} &nbsp;on {chainName} {isProxy(contract) ? 'with proxy' : ''}
</span> </span>
</button> </button>
</h3> </h3>
<div id={`collapse${index}`} className={`accordion-collapse p-2 collapse ${expanded ? 'show' : ''}`} aria-labelledby={`heading${index}`} data-bs-parent="#receiptsAccordion"> <div id={`collapse${index}`} className={`accordion-collapse p-2 collapse ${expanded ? 'show' : ''}`} aria-labelledby={`heading${index}`} data-bs-parent="#receiptsAccordion">
<div className="accordion-body"> <div className="accordion-body">
<div> {isContract(contract) ? (
<span className="font-weight-bold">Chain ID: </span> <ReceiptsBody contract={contract}></ReceiptsBody>
{contract.chainId} ) : (
</div> <>
<div> <div>
<span className="font-weight-bold">File: </span> <span className="font-weight-bold" style={{ fontSize: '1.2rem' }}>
{contract.filePath} Implementation
</div> </span>
<div> <ReceiptsBody contract={contract.implementation}></ReceiptsBody>
<span className="font-weight-bold">Contract: </span> </div>
{contract.contractName} <div className="mt-3">
</div> <span className="font-weight-bold" style={{ fontSize: '1.2rem' }}>
<div> Proxy
<span className="font-weight-bold">Submission: </span> </span>
{contract.date.toLocaleString()} &nbsp;
</div> <CustomTooltip tooltipText={contract.proxy.address}>
<div className="table-responsive"> <span>{shortenAddress(contract.proxy.address)}</span>
<table className="table"> </CustomTooltip>
<thead> <ReceiptsBody contract={contract.proxy}></ReceiptsBody>
<tr> </div>
<th>Verifier</th> </>
<th>API URL</th> )}
<th>Status</th>
<th>Message</th>
<th>ReceiptID</th>
</tr>
</thead>
<tbody>
{contract.receipts.map((receipt) => (
<tr key={receipt.receiptId}>
<td>{receipt.verifier.name}</td>
<td>{receipt.verifier.apiUrl}</td>
<td>{receipt.status}</td>
<td>{receipt.message}</td>
<td>{receipt.receiptId}</td>
</tr>
))}
</tbody>
</table>
</div>
</div> </div>
</div> </div>
</div> </div>
) )
} }
const ReceiptsBody = ({ contract }: { contract: SubmittedContract }) => {
return (
<>
<div>
<span className="font-weight-bold">Chain ID: </span>
{contract.chainId}
</div>
<div>
<span className="font-weight-bold">File: </span>
{contract.filePath}
</div>
<div>
<span className="font-weight-bold">Contract: </span>
{contract.contractName}
</div>
<div>
<span className="font-weight-bold">Submission: </span>
{contract.date.toLocaleString()}
</div>
<div className="table-responsive">
<table className="table">
<thead>
<tr>
<th>Verifier</th>
<th>API URL</th>
<th>Status</th>
<th>Message</th>
<th>ReceiptID</th>
</tr>
</thead>
<tbody>
{contract.receipts.map((receipt) => (
<tr key={receipt.receiptId}>
<td>{receipt.verifier.name}</td>
<td>{receipt.verifier.apiUrl}</td>
<td>{receipt.status}</td>
<td>{receipt.message}</td>
<td>{receipt.receiptId}</td>
</tr>
))}
</tbody>
</table>
</div>
</>
)
}

@ -32,6 +32,7 @@ export interface VerificationReceipt {
} }
export interface SubmittedContract { export interface SubmittedContract {
type: 'contract'
id: string id: string
filePath: string filePath: string
contractName: string contractName: string
@ -42,8 +43,23 @@ export interface SubmittedContract {
receipts: VerificationReceipt[] receipts: VerificationReceipt[]
} }
export interface SubmittedProxyContract {
type: 'proxy'
id: string
implementation: SubmittedContract
proxy: SubmittedContract
}
export interface SubmittedContracts { export interface SubmittedContracts {
[id: string]: SubmittedContract [id: string]: SubmittedContract | SubmittedProxyContract
}
export function isProxy(contract: SubmittedContract | SubmittedProxyContract): contract is SubmittedProxyContract {
return contract.type === 'proxy'
}
export function isContract(contract: SubmittedContract | SubmittedProxyContract): contract is SubmittedContract {
return contract.type === 'contract'
} }
export interface SourcifyVerificationResponse { export interface SourcifyVerificationResponse {

@ -1,26 +1,15 @@
import React from 'react'
import { AppContext } from '../AppContext'
import example from './example.js' import example from './example.js'
import { AccordionReceipt } from '../components/AccordionReceipt' import { AccordionReceipt } from '../components/AccordionReceipt'
import { SubmittedContract } from '../types/VerificationTypes' import { SubmittedContracts } from '../types/VerificationTypes'
export const ReceiptsView = () => { export const ReceiptsView = () => {
const { chains } = React.useContext(AppContext) const submittedContracts = example as unknown as SubmittedContracts
// const {submittedContracts} = React.useContext(AppContext); // const {submittedContracts} = React.useContext(AppContext);
const getChainName = (chainId: string) => {
return chains.find((chain) => chain.chainId === parseInt(chainId))?.name ?? 'Unknown Chain'
}
const submittedContracts = example as unknown as Record<string,SubmittedContract>
console.log('submittedContracts', submittedContracts)
return ( return (
<div className="accordion" id="receiptsAccordion"> <div className="accordion" id="receiptsAccordion">
{Object.values(submittedContracts).map((contract, index) => ( {Object.values(submittedContracts).map((contract, index) => (
<AccordionReceipt contract={contract} index={index} chainName={getChainName(contract.chainId)} /> <AccordionReceipt contract={contract} index={index} />
))}
{Object.values(submittedContracts).map((contract, index) => (
<AccordionReceipt contract={contract} index={index+1} chainName={getChainName(contract.chainId)} />
))} ))}
</div> </div>
) )

@ -51,6 +51,7 @@ export const VerifyView = () => {
// A receipt for each verifier // A receipt for each verifier
const receipts: VerificationReceipt[] = enabledVerifiers.map((verifier) => ({ verifier, status: null, receiptId: null, message: null })) const receipts: VerificationReceipt[] = enabledVerifiers.map((verifier) => ({ verifier, status: null, receiptId: null, message: null }))
const newSubmittedContract: SubmittedContract = { const newSubmittedContract: SubmittedContract = {
type: 'contract',
id: selectedChain?.chainId + '-' + contractAddress + '-' + date.toString(), id: selectedChain?.chainId + '-' + contractAddress + '-' + date.toString(),
address: contractAddress, address: contractAddress,
chainId: selectedChain?.chainId.toString(), chainId: selectedChain?.chainId.toString(),

@ -1,5 +1,6 @@
const json = { const json = {
'undefined-0x2738d13E81e30bC615766A0410e7cF199FD59A83-Thu Jun 20 2024 22:32:36 GMT+0200 (Central European Summer Time)': { 'undefined-0x2738d13E81e30bC615766A0410e7cF199FD59A83-Thu Jun 20 2024 22:32:36 GMT+0200 (Central European Summer Time)': {
type: 'contract',
id: 'undefined-0x2738d13E81e30bC615766A0410e7cF199FD59A83-Thu Jun 20 2024 22:32:36 GMT+0200 (Central European Summer Time)', id: 'undefined-0x2738d13E81e30bC615766A0410e7cF199FD59A83-Thu Jun 20 2024 22:32:36 GMT+0200 (Central European Summer Time)',
address: '0x2738d13E81e30bC615766A0410e7cF199FD59A83', address: '0x2738d13E81e30bC615766A0410e7cF199FD59A83',
filePath: '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol', filePath: '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol',
@ -28,7 +29,75 @@ const json = {
message: 'Failed to fetch', message: 'Failed to fetch',
}, },
], ],
chainId: "1" chainId: '1',
},
'1-0x2738d13E81e30bC615766A0410e7cF199FD59A83-Thu Jun 20 2024 22:32:36 GMT+0200 (Central European Summer Time)': {
type: 'proxy',
id: '1-0x2738d13E81e30bC615766A0410e7cF199FD59A83-Thu Jun 20 2024 22:32:36 GMT+0200 (Central European Summer Time)',
implementation: {
type: 'contract',
id: 'undefined-0x2738d13E81e30bC615766A0410e7cF199FD59A83-Thu Jun 20 2024 22:32:36 GMT+0200 (Central European Summer Time)',
address: '0x2738d13E81e30bC615766A0410e7cF199FD59A83',
filePath: '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol',
contractName: 'Initializable',
date: '2024-06-20T20:32:36.361Z',
receipts: [
{
verifier: {
name: 'Sourcify Localhost',
apiUrl: 'http://localhost:5555/',
enabled: true,
},
status: 'error',
receiptId: null,
message: 'Failed to fetch',
},
{
verifier: {
name: 'Etherscan',
apiUrl: 'https://api.etherscan.io',
enabled: true,
apiKey: 'API_KEY',
},
status: 'error',
receiptId: null,
message: 'Failed to fetch',
},
],
chainId: '1',
},
proxy: {
type: 'contract',
id: 'undefined-0x2738d13E81e30bC615766A0410e7cF199FD59A83-Thu Jun 20 2024 22:32:36 GMT+0200 (Central European Summer Time)',
address: '0x2738d13E81e30bC615766A0410e7cF199FD59A83',
filePath: '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol',
contractName: 'Initializable',
date: '2024-06-20T20:32:36.361Z',
receipts: [
{
verifier: {
name: 'Sourcify Localhost',
apiUrl: 'http://localhost:5555/',
enabled: true,
},
status: 'error',
receiptId: null,
message: 'Failed to fetch',
},
{
verifier: {
name: 'Etherscan',
apiUrl: 'https://api.etherscan.io',
enabled: true,
apiKey: 'API_KEY',
},
status: 'error',
receiptId: null,
message: 'Failed to fetch',
},
],
chainId: '1',
},
}, },
} }

Loading…
Cancel
Save