Skip to content

Commit

Permalink
Merge pull request #3750 from ethereum/proxyConVerif
Browse files Browse the repository at this point in the history
Enable proxy contract verification in Etherscan plugin
  • Loading branch information
yann300 authored Jun 7, 2023
2 parents a665e31 + 5bda14c commit 5b2174c
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 31 deletions.
2 changes: 1 addition & 1 deletion apps/etherscan/src/app/RemixPlugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export class RemixClient extends PluginClient {
}

async verify (apiKey: string, contractAddress: string, contractArguments: string, contractName: string, compilationResultParam: any) {
const result = await verify(apiKey, contractAddress, contractArguments, contractName, compilationResultParam, null, this,
const result = await verify(apiKey, contractAddress, contractArguments, contractName, compilationResultParam, null, false, null, this,
(value: EtherScanReturn) => {}, (value: string) => {})
return result
}
Expand Down
32 changes: 23 additions & 9 deletions apps/etherscan/src/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { DisplayRoutes } from "./routes"

import { useLocalStorage } from "./hooks/useLocalStorage"

import { getReceiptStatus, getEtherScanApi, getNetworkName } from "./utils"
import { getReceiptStatus, getEtherScanApi, getNetworkName, getProxyContractReceiptStatus } from "./utils"
import { Receipt, ThemeType } from "./types"

import "./App.css"
Expand Down Expand Up @@ -102,19 +102,33 @@ const App = () => {
let newReceipts = receipts
for (const item of receiptsNotVerified) {
await new Promise(r => setTimeout(r, 500)) // avoid api rate limit exceed.
console.log('checking receipt', item.guid)
const status = await getReceiptStatus(
item.guid,
apiKey,
getEtherScanApi(networkId)
)
if (status.result === "Pass - Verified" || status.result === "Already Verified") {
let status
if (item.isProxyContract) {
status = await getProxyContractReceiptStatus(
item.guid,
apiKey,
getEtherScanApi(networkId)
)
if (status.status === '1') {
status.message = status.result
status.result = 'Successfully Updated'
}
} else
status = await getReceiptStatus(
item.guid,
apiKey,
getEtherScanApi(networkId)
)
if (status.result === "Pass - Verified" || status.result === "Already Verified" ||
status.result === "Successfully Updated") {
newReceipts = newReceipts.map((currentReceipt: Receipt) => {
if (currentReceipt.guid === item.guid) {
return {
let res = {
...currentReceipt,
status: status.result,
}
if (currentReceipt.isProxyContract) res.message = status.message
return res
}
return currentReceipt
})
Expand Down
5 changes: 4 additions & 1 deletion apps/etherscan/src/app/types/Receipt.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
export type ReceiptStatus = "Pending in queue" | "Pass - Verified" | "Already Verified" | "Max rate limit reached"
export type ReceiptStatus = "Pending in queue" | "Pass - Verified" | "Already Verified" | "Max rate limit reached" | "Successfully Updated"

export interface Receipt {
guid: string
status: ReceiptStatus
isProxyContract: boolean
message?: string
succeed?: boolean
}
19 changes: 19 additions & 0 deletions apps/etherscan/src/app/utils/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,22 @@ export const getReceiptStatus = async (
console.error(error)
}
}

export const getProxyContractReceiptStatus = async (
receiptGuid: string,
apiKey: string,
etherscanApi: string
): Promise<receiptStatus> => {
const params = `guid=${receiptGuid}&module=contract&action=checkproxyverification&apiKey=${apiKey}`
try {
const response = await axios.get(`${etherscanApi}?${params}`)
const { result, message, status } = response.data
return {
result,
message,
status,
}
} catch (error) {
console.error(error)
}
}
34 changes: 28 additions & 6 deletions apps/etherscan/src/app/utils/verify.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getNetworkName, getEtherScanApi, getReceiptStatus } from "../utils"
import { getNetworkName, getEtherScanApi, getReceiptStatus, getProxyContractReceiptStatus } from "../utils"
import { CompilationResult } from "@remixproject/plugin-api"
import { CompilerAbstract } from '@remix-project/remix-solidity'
import axios from 'axios'
Expand All @@ -21,7 +21,9 @@ export const verify = async (
contractArgumentsParam: string,
contractName: string,
compilationResultParam: CompilerAbstract,
chainRef: number | string,
chainRef: number | string,
isProxyContract: boolean,
expectedImplAddress: string,
client: PluginClient,
onVerifiedContract: (value: EtherScanReturn) => void,
setResults: (value: string) => void
Expand Down Expand Up @@ -85,13 +87,20 @@ export const verify = async (
module: "contract", // Do not change
action: "verifysourcecode", // Do not change
codeformat: "solidity-standard-json-input",
contractaddress: contractAddress, // Contract Address starts with 0x...
sourceCode: JSON.stringify(jsonInput),
contractname: fileName + ':' + contractName,
compilerversion: `v${contractMetadataParsed.compiler.version}`, // see http://etherscan.io/solcversions for list of support versions
constructorArguements: contractArgumentsParam ? contractArgumentsParam.replace('0x', '') : '', // if applicable
}

if (isProxyContract) {
data.action = "verifyproxycontract"
data.expectedimplementation = expectedImplAddress
data.address = contractAddress
} else {
data.contractaddress = contractAddress
}

const body = new FormData()
Object.keys(data).forEach((key) => body.append(key, data[key]))

Expand All @@ -105,7 +114,18 @@ export const verify = async (

if (message === "OK" && status === "1") {
resetAfter10Seconds(client, setResults)
const receiptStatus = await getReceiptStatus(
let receiptStatus
if (isProxyContract) {
receiptStatus = await getProxyContractReceiptStatus(
result,
apiKeyParam,
etherscanApi
)
if (receiptStatus.status === '1') {
receiptStatus.message = receiptStatus.result
receiptStatus.result = 'Successfully Updated'
}
} else receiptStatus = await getReceiptStatus(
result,
apiKeyParam,
etherscanApi
Expand All @@ -115,7 +135,8 @@ export const verify = async (
guid: result,
status: receiptStatus.result,
message: `Verification process started correctly. Receipt GUID ${result}`,
succeed: true
succeed: true,
isProxyContract
}
onVerifiedContract(returnValue)
return returnValue
Expand All @@ -127,7 +148,8 @@ export const verify = async (
})
const returnValue = {
message: result,
succeed: false
succeed: false,
isProxyContract
}
resetAfter10Seconds(client, setResults)
return returnValue
Expand Down
73 changes: 60 additions & 13 deletions apps/etherscan/src/app/views/ReceiptsView.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import React, { useState } from "react"

import { Formik, ErrorMessage, Field } from "formik"
import { getEtherScanApi, getNetworkName, getReceiptStatus } from "../utils"
import { getEtherScanApi, getNetworkName, getReceiptStatus, getProxyContractReceiptStatus } from "../utils"
import { Receipt } from "../types"
import { AppContext } from "../AppContext"
import { SubmitButton } from "../components"
import { Navigate } from "react-router-dom"
import { Button } from "react-bootstrap"
import { CustomTooltip } from '@remix-ui/helper'

interface FormValues {
receiptGuid: string
}

export const ReceiptsView: React.FC = () => {
const [results, setResults] = useState({succeed: false, message: ''})
const [isProxyContractReceipt, setIsProxyContractReceipt] = useState(false)

const onGetReceiptStatus = async (
values: FormValues,
clientInstance: any,
Expand All @@ -25,15 +28,27 @@ export const ReceiptsView: React.FC = () => {
setResults({
succeed: false,
message: "Cannot verify in the selected network"
})
})
return
}
const etherscanApi = getEtherScanApi(networkId)
const result = await getReceiptStatus(
values.receiptGuid,
apiKey,
etherscanApi
)
let result
if (isProxyContractReceipt) {
result = await getProxyContractReceiptStatus(
values.receiptGuid,
apiKey,
etherscanApi
)
if (result.status === '1') {
result.message = result.result
result.result = 'Successfully Updated'
}
} else
result = await getReceiptStatus(
values.receiptGuid,
apiKey,
etherscanApi
)
setResults({
succeed: result.status === '1' ? true : false,
message: result.result || (result.status === '0' ? 'Verification failed' : result.message)
Expand Down Expand Up @@ -71,7 +86,7 @@ export const ReceiptsView: React.FC = () => {
onGetReceiptStatus(values, clientInstance, apiKey)
}
>
{({ errors, touched, handleSubmit }) => (
{({ errors, touched, handleSubmit, handleChange }) => (
<form onSubmit={handleSubmit}>
<div
className="form-group"
Expand All @@ -93,6 +108,21 @@ export const ReceiptsView: React.FC = () => {
component="div"
/>
</div>

<div className="d-flex mb-2 custom-control custom-checkbox">
<Field
className="custom-control-input"
type="checkbox"
name="isProxyReceipt"
id="isProxyReceipt"
onChange={async (e) => {
handleChange(e)
if (e.target.checked) setIsProxyContractReceipt(true)
else setIsProxyContractReceipt(false)
}}
/>
<label className="form-check-label custom-control-label" htmlFor="isProxyReceipt">It's a proxy contract GUID</label>
</div>
<SubmitButton text="Check" disable = {!touched.receiptGuid || (touched.receiptGuid && errors.receiptGuid) ? true : false} />
</form>
)}
Expand All @@ -108,8 +138,14 @@ export const ReceiptsView: React.FC = () => {
dangerouslySetInnerHTML={{ __html: results.message ? results.message : '' }}
/>

<ReceiptsTable receipts={receipts} />
<Button title="Clear the list of receipt" onClick={() => { setReceipts([]) }} >Clear</Button>
<ReceiptsTable receipts={receipts} /><br/>
<CustomTooltip
tooltipText="Clear the list of receipts"
tooltipId='etherscan-clear-receipts'
placement='bottom'
>
<Button onClick={() => { setReceipts([]) }} >Clear</Button>
</CustomTooltip>
</div>
)
}
Expand All @@ -120,7 +156,7 @@ export const ReceiptsView: React.FC = () => {

const ReceiptsTable: React.FC<{ receipts: Receipt[] }> = ({ receipts }) => {
return (
<div className="table-responsive" style={{ fontSize: "0.7em" }}>
<div className="table-responsive" style={{ fontSize: "0.8em" }}>
<h6>Receipts</h6>
<table className="table table-sm">
<thead>
Expand All @@ -135,10 +171,21 @@ const ReceiptsTable: React.FC<{ receipts: Receipt[] }> = ({ receipts }) => {
receipts.map((item: Receipt, index) => {
return (
<tr key={item.guid}>
<td className={item.status === 'Pass - Verified'
<td className={(item.status === 'Pass - Verified' || item.status === 'Successfully Updated')
? 'text-success' : (item.status === 'Pending in queue'
? 'text-warning' : (item.status === 'Already Verified'
? 'text-info': 'text-secondary'))}>{item.status}</td>
? 'text-info': 'text-secondary'))}>
{item.status}
{item.status === 'Successfully Updated' && <CustomTooltip
placement={'bottom'}
tooltipClasses="text-wrap"
tooltipId="etherscan-receipt-proxy-status"
tooltipText={item.message}
>
<i style={{ fontSize: 'small' }} className={'ml-1 fal fa-info-circle align-self-center'} aria-hidden="true"></i>
</CustomTooltip>
}
</td>
<td>{item.guid}</td>
</tr>
)
Expand Down
45 changes: 44 additions & 1 deletion apps/etherscan/src/app/views/VerifyView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ interface Props {
interface FormValues {
contractName: string
contractAddress: string
expectedImplAddress?: string
}

export const VerifyView: React.FC<Props> = ({
Expand All @@ -33,6 +34,7 @@ export const VerifyView: React.FC<Props> = ({
const [results, setResults] = useState("")
const [networkName, setNetworkName] = useState("Loading...")
const [showConstructorArgs, setShowConstructorArgs] = useState(false)
const [isProxyContract, setIsProxyContract] = useState(false)
const [constructorInputs, setConstructorInputs] = useState([])
const verificationResult = useRef({})

Expand Down Expand Up @@ -74,6 +76,8 @@ export const VerifyView: React.FC<Props> = ({
values.contractName,
compilationResult,
null,
isProxyContract,
values.expectedImplAddress,
client,
onVerifiedContract,
setResults,
Expand All @@ -86,7 +90,7 @@ export const VerifyView: React.FC<Props> = ({
<Formik
initialValues={{
contractName: "",
contractAddress: "",
contractAddress: ""
}}
validate={(values) => {
const errors = {} as any
Expand Down Expand Up @@ -206,6 +210,45 @@ export const VerifyView: React.FC<Props> = ({
/>
</div>

<div className="d-flex mb-2 custom-control custom-checkbox">
<Field
className="custom-control-input"
type="checkbox"
name="isProxy"
id="isProxy"
onChange={async (e) => {
handleChange(e)
if (e.target.checked) setIsProxyContract(true)
else setIsProxyContract(false)
}}
/>
<label className="form-check-label custom-control-label" htmlFor="isProxy">It's a proxy contract</label>
</div>

<div className={ isProxyContract ? 'form-group d-block': 'form-group d-none' }>
<label htmlFor="expectedImplAddress">Expected Implementation Address</label>
<CustomTooltip
placement={'top'}
tooltipClasses="text-wrap"
tooltipId="etherscan-impl-address-info"
tooltipText="Make sure implementation contract is already verified before proxy contract"
>
<i style={{ fontSize: 'small' }} className={'ml-1 fal fa-info-circle align-self-center'} aria-hidden="true"></i>
</CustomTooltip>
<CustomTooltip
tooltipText='Providing expected implementation address enforces a check to ensure the returned implementation contract address is same as address picked up by the verifier'
tooltipId='etherscan-impl-address'
placement='bottom'
>
<Field
className="form-control"
type="text"
name="expectedImplAddress"
placeholder="e.g. 0x11b79afc03baf25c631dd70169bb6a3160b2706e"
/>
</CustomTooltip>
</div>

<SubmitButton dataId="verify-contract" text="Verify"
isSubmitting={isSubmitting}
disable={ !contracts.length ||
Expand Down

0 comments on commit 5b2174c

Please sign in to comment.