Correctly encode and decode constructor arguments

pull/5285/head
Manuel Wedler 4 months ago committed by Aniket
parent 590fd6d253
commit 9ff971d209
  1. 2
      apps/contract-verification/src/app/Verifiers/EtherscanVerifier.ts
  2. 78
      apps/contract-verification/src/app/components/ConstructorArguments.tsx
  3. 3
      apps/contract-verification/src/app/views/VerifyView.tsx

@ -39,7 +39,7 @@ export class EtherscanVerifier extends AbstractVerifier {
formData.append('contractaddress', submittedContract.address)
formData.append('contractname', submittedContract.filePath + ':' + submittedContract.contractName)
formData.append('compilerversion', `v${metadata.compiler.version}`)
formData.append('constructorArguements', submittedContract.abiEncodedConstructorArgs ?? '')
formData.append('constructorArguements', submittedContract.abiEncodedConstructorArgs.replace('0x', '') ?? '')
const url = new URL(this.apiUrl + '/api')
url.searchParams.append('module', 'contract')

@ -1,84 +1,90 @@
import React, { useEffect } from 'react'
import { useContext, useEffect, useState } from 'react'
import { ethers } from 'ethers'
import { AppContext } from '../AppContext'
import { ContractDropdownSelection } from './ContractDropdown'
const abiCoder = new ethers.utils.AbiCoder()
interface ConstructorArgumentsProps {
abiEncodedConstructorArgs: string
setAbiEncodedConstructorArgs: React.Dispatch<React.SetStateAction<string>>
abiEncodingError: string
setAbiEncodingError: React.Dispatch<React.SetStateAction<string>>
selectedContract: ContractDropdownSelection
}
// TODO
// Add mapping VerifierIdentifier -> ConstructorArgsRequired
// Check enabledVerifiers: when not required, don't show component and set to null
export const ConstructorArguments: React.FC<ConstructorArgumentsProps> = ({ abiEncodedConstructorArgs, setAbiEncodedConstructorArgs, selectedContract }) => {
const { compilationOutput } = React.useContext(AppContext)
const [constructorArgsValues, setConstructorArgsValues] = React.useState<string[]>([])
const [abiEncodingError, setAbiEncodingError] = React.useState<string | null>('')
const [toggleRawInput, setToggleRawInput] = React.useState<boolean>(false)
export const ConstructorArguments: React.FC<ConstructorArgumentsProps> = ({ abiEncodedConstructorArgs, setAbiEncodedConstructorArgs, abiEncodingError, setAbiEncodingError, selectedContract }) => {
const { compilationOutput } = useContext(AppContext)
const [toggleRawInput, setToggleRawInput] = useState<boolean>(false)
const { triggerFilePath, filePath, contractName } = selectedContract
const selectedCompilerAbstract = triggerFilePath && compilationOutput[triggerFilePath]
const compiledContract = selectedCompilerAbstract?.data?.contracts?.[filePath]?.[contractName]
const abi = compiledContract?.abi
// Wanted to use execution.txHelper.getConstructorInterface from @remix-project/remix-lib but getting errors: 'error getting eth provider options', 'global is not defined' etc.
const constructorArgs = abi && abi.find((a) => a.type === 'constructor') && abi.find((a) => a.type === 'constructor').inputs
const constructorArgs = abi && abi.find((a) => a.type === 'constructor')?.inputs
const [constructorArgsValues, setConstructorArgsValues] = useState<string[]>(Array(constructorArgs?.length ?? 0).fill(''))
useEffect(() => {
if (!constructorArgs) {
setConstructorArgsValues([])
return
setConstructorArgsValues([])
setAbiEncodedConstructorArgs('')
setAbiEncodingError('')
if (constructorArgs) {
setConstructorArgsValues(Array(constructorArgs.length).fill(''))
}
setConstructorArgsValues(Array(constructorArgs.length).fill(''))
}, [constructorArgs])
// Do the abi encoding when user values are input
useEffect(() => {
if (!constructorArgs) {
const handleConstructorArgs = (value: string, index: number) => {
const changedConstructorArgsValues = [...constructorArgsValues.slice(0, index), value, ...constructorArgsValues.slice(index + 1)]
setConstructorArgsValues(changedConstructorArgsValues)
// if any constructorArgsValue is falsey (empty etc.), don't encode yet
if (changedConstructorArgsValues.some((value) => !value)) {
setAbiEncodedConstructorArgs('')
setAbiEncodingError('')
return
}
if (constructorArgsValues.length !== constructorArgs.length) return
// if any constructorArgsValue is falsey (empty etc.), don't encode yet
if (constructorArgsValues.some((value) => !value)) return setAbiEncodingError('')
const types = constructorArgs.map((inp) => inp.type)
const parsedArgsValues = []
for (const arg of changedConstructorArgsValues) {
try {
parsedArgsValues.push(JSON.parse(arg))
} catch (e) {
parsedArgsValues.push(arg)
}
}
try {
console.log('constructorArgsValues', constructorArgsValues)
console.log('types', types)
const newAbiEncoding = abiCoder.encode(types, constructorArgsValues)
const newAbiEncoding = ethers.utils.defaultAbiCoder.encode(types, parsedArgsValues)
setAbiEncodedConstructorArgs(newAbiEncoding)
setAbiEncodingError('')
} catch (e) {
console.error(e)
setAbiEncodedConstructorArgs('')
setAbiEncodingError('Encoding error: ' + e.message)
}
}, [constructorArgsValues, constructorArgs])
if (!selectedContract) return null
if (!compilationOutput && Object.keys(compilationOutput).length === 0) return null
// No render if no constructor args
if (!constructorArgs || constructorArgs.length === 0) return null
}
const handleRawConstructorArgs = (value: string) => {
setAbiEncodedConstructorArgs(value)
try {
const decoded = abiCoder.decode(
const decoded = ethers.utils.defaultAbiCoder.decode(
constructorArgs.map((inp) => inp.type),
value
)
setConstructorArgsValues(decoded.map((val) => val.toString()))
setConstructorArgsValues(decoded.map((val) => JSON.stringify(val)))
setAbiEncodingError('')
} catch (e) {
console.error(e)
setAbiEncodingError('Decoding error: ' + e.message)
}
}
if (!selectedContract) return null
if (!compilationOutput && Object.keys(compilationOutput).length === 0) return null
// No render if no constructor args
if (!constructorArgs || constructorArgs.length === 0) return null
return (
<div className="mt-4">
<label>Constructor Arguments</label>
@ -99,7 +105,7 @@ export const ConstructorArguments: React.FC<ConstructorArgumentsProps> = ({ abiE
{constructorArgs.map((inp, i) => (
<div key={`constructor-arg-${inp.name}`} className="d-flex flex-row align-items-center mb-2">
<div className="mr-2 small">{inp.name}</div>
<input className="form-control" placeholder={inp.type} value={constructorArgsValues[i] || ''} onChange={(e) => setConstructorArgsValues([...constructorArgsValues.slice(0, i), e.target.value, ...constructorArgsValues.slice(i + 1)])} />
<input className="form-control" placeholder={inp.type} value={constructorArgsValues[i]} onChange={(e) => handleConstructorArgs(e.target.value, i)} />
</div>
))}
{abiEncodedConstructorArgs && (

@ -17,6 +17,7 @@ export const VerifyView = () => {
const [contractAddress, setContractAddress] = useState('')
const [contractAddressError, setContractAddressError] = useState('')
const [abiEncodedConstructorArgs, setAbiEncodedConstructorArgs] = useState<string>('')
const [abiEncodingError, setAbiEncodingError] = useState<string>('')
const [selectedContract, setSelectedContract] = useState<ContractDropdownSelection | undefined>()
const [enabledVerifiers, setEnabledVerifiers] = useState<Partial<Record<VerifierIdentifier, boolean>>>({})
const navigate = useNavigate()
@ -113,7 +114,7 @@ export const VerifyView = () => {
<ContractDropdown label="Contract Name" id="contract-dropdown-1" setSelectedContract={setSelectedContract} />
{selectedContract && <ConstructorArguments abiEncodedConstructorArgs={abiEncodedConstructorArgs} setAbiEncodedConstructorArgs={setAbiEncodedConstructorArgs} selectedContract={selectedContract} />}
{selectedContract && <ConstructorArguments abiEncodedConstructorArgs={abiEncodedConstructorArgs} setAbiEncodedConstructorArgs={setAbiEncodedConstructorArgs} selectedContract={selectedContract} abiEncodingError={abiEncodingError} setAbiEncodingError={setAbiEncodingError} />}
<div>
Verify on:

Loading…
Cancel
Save