Skip to content

Commit

Permalink
fix: integrate etherscan verification into executor
Browse files Browse the repository at this point in the history
  • Loading branch information
sam-goldman committed Nov 11, 2022
1 parent 3c939bd commit 7b33791
Show file tree
Hide file tree
Showing 10 changed files with 180 additions and 222 deletions.
6 changes: 6 additions & 0 deletions .changeset/honest-shrimps-bake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@chugsplash/executor': patch
'@chugsplash/plugins': patch
---

Integrate etherscan verification into executor
5 changes: 2 additions & 3 deletions packages/core/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
ChugSplashRegistryABI,
ChugSplashManagerABI,
ChugSplashManagerProxyArtifact,
// CHUGSPLASH_REGISTRY_ADDRESS,
CHUGSPLASH_REGISTRY_PROXY_ADDRESS,
} from '@chugsplash/contracts'

Expand Down Expand Up @@ -69,14 +68,14 @@ export const writeDeploymentArtifact = (

export const getProxyAddress = (
projectName: string,
target: string
referenceName: string
): string => {
// const chugSplashManagerAddress = getChugSplashManagerAddress(projectName)
const chugSplashManagerAddress = getChugSplashManagerProxyAddress(projectName)

return utils.getCreate2Address(
chugSplashManagerAddress,
utils.keccak256(utils.toUtf8Bytes(target)),
utils.keccak256(utils.toUtf8Bytes(referenceName)),
utils.solidityKeccak256(
['bytes', 'bytes'],
[
Expand Down
7 changes: 6 additions & 1 deletion packages/executor/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import {
CHUGSPLASH_REGISTRY_PROXY_ADDRESS,
} from '@chugsplash/contracts'
import { ChugSplashBundleState } from '@chugsplash/core'
import { getChainId } from '@eth-optimism/core-utils'

import { compileRemoteBundle } from './utils'
import { compileRemoteBundle, verifyChugSplashConfig } from './utils'

type Options = {
network: string
Expand Down Expand Up @@ -116,6 +117,10 @@ export class ChugSplashExecutor extends BaseServiceV2<Options, Metrics, State> {
deployer: signer,
hide: false,
})

if ((await getChainId(this.state.wallet.provider)) !== 31337) {
await verifyChugSplashConfig(hre, proposalEvent.args.configUri)
}
}
}
}
Expand Down
99 changes: 8 additions & 91 deletions packages/executor/src/utils/compile.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
import * as dotenv from 'dotenv'
import { SolcBuild } from 'hardhat/types'
import {
TASK_COMPILE_SOLIDITY_GET_SOLC_BUILD,
TASK_COMPILE_SOLIDITY_RUN_SOLCJS,
TASK_COMPILE_SOLIDITY_RUN_SOLC,
} from 'hardhat/builtin-tasks/task-names'
import { add0x } from '@eth-optimism/core-utils'
import {
CanonicalChugSplashConfig,
ChugSplashActionBundle,
} from '@chugsplash/core'
import { create } from 'ipfs-http-client'
import { ContractArtifact } from '@chugsplash/plugins'
import {
TASK_CHUGSPLASH_FETCH,
TASK_CHUGSPLASH_BUNDLE_REMOTE,
} from '@chugsplash/plugins'

// Load environment variables from .env
dotenv.config()
Expand All @@ -30,89 +25,11 @@ export const compileRemoteBundle = async (
bundle: ChugSplashActionBundle
canonicalConfig: CanonicalChugSplashConfig
}> => {
const canonicalConfig = await fetchChugSplashConfig(configUri)
const bundle = await hre.run('chugsplash-bundle-remote', {
const canonicalConfig = await hre.run(TASK_CHUGSPLASH_FETCH, {
configUri,
})
const bundle = await hre.run(TASK_CHUGSPLASH_BUNDLE_REMOTE, {
canonicalConfig,
})
return { bundle, canonicalConfig }
}

export const getArtifactsFromCanonicalConfig = async (
hre: any,
canonicalConfig: CanonicalChugSplashConfig
): Promise<{
[contractName: string]: ContractArtifact
}> => {
const artifacts = {}
for (const source of canonicalConfig.inputs) {
const solcBuild: SolcBuild = await hre.run(
TASK_COMPILE_SOLIDITY_GET_SOLC_BUILD,
{
quiet: true,
solcVersion: source.solcVersion,
}
)

let output: any // TODO: Compiler output
if (solcBuild.isSolcJs) {
output = await hre.run(TASK_COMPILE_SOLIDITY_RUN_SOLCJS, {
input: source.input,
solcJsPath: solcBuild.compilerPath,
})
} else {
output = await hre.run(TASK_COMPILE_SOLIDITY_RUN_SOLC, {
input: source.input,
solcPath: solcBuild.compilerPath,
})
}

for (const [sourceName, fileOutput] of Object.entries(output.contracts)) {
for (const [contractName, contractOutput] of Object.entries(fileOutput)) {
artifacts[contractName] = {
bytecode: add0x(contractOutput.evm.bytecode.object),
storageLayout: contractOutput.storageLayout,
contractName,
sourceName,
abi: contractOutput.abi,
sources: output.sources,
immutableReferences:
output.contracts[sourceName][contractName].evm.deployedBytecode
.immutableReferences,
}
}
}
}
return artifacts
}

// TODO: change file name or add another file
export const fetchChugSplashConfig = async (
configUri: string
): Promise<CanonicalChugSplashConfig> => {
const projectCredentials = `${process.env.IPFS_PROJECT_ID}:${process.env.IPFS_API_KEY_SECRET}`
const ipfs = create({
host: 'ipfs.infura.io',
port: 5001,
protocol: 'https',
headers: {
authorization: `Basic ${Buffer.from(projectCredentials).toString(
'base64'
)}`,
},
})

let config: CanonicalChugSplashConfig
if (configUri.startsWith('ipfs://')) {
const decoder = new TextDecoder()
let data = ''
const stream = await ipfs.cat(configUri.replace('ipfs://', ''))
for await (const chunk of stream) {
// Chunks of data are returned as a Uint8Array. Convert it back to a string
data += decoder.decode(chunk, { stream: true })
}
config = JSON.parse(data)
} else {
throw new Error('unsupported URI type')
}
return config
}
46 changes: 29 additions & 17 deletions packages/executor/src/utils/etherscan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,17 @@ import assert from 'assert'

import { Contract } from 'ethers'
import {
CanonicalChugSplashConfig,
CompilerInput,
getChugSplashManagerProxyAddress,
getProxyAddress,
parseChugSplashConfig,
} from '@chugsplash/core'
import { getConstructorArgs } from '@chugsplash/plugins'
import {
getConstructorArgs,
TASK_CHUGSPLASH_FETCH,
getArtifactsFromParsedCanonicalConfig,
} from '@chugsplash/plugins'
import { TASK_VERIFY_GET_ETHERSCAN_ENDPOINT } from '@nomiclabs/hardhat-etherscan/dist/src/constants'
import { EtherscanURLs } from '@nomiclabs/hardhat-etherscan/dist/src/types'
import {
Expand All @@ -28,11 +35,6 @@ import { ChugSplashManagerABI } from '@chugsplash/contracts'
import { EthereumProvider } from 'hardhat/types'
import { request } from 'undici'

import {
fetchChugSplashConfig,
getArtifactsFromCanonicalConfig,
} from './compile'

export interface EtherscanResponseBody {
status: string
message: string
Expand All @@ -44,30 +46,32 @@ export const RESPONSE_OK = '1'
export const verifyChugSplashConfig = async (hre: any, configUri: string) => {
const { etherscanApiKey, etherscanApiEndpoints } = await getEtherscanInfo(hre)

const canonicalConfig = await fetchChugSplashConfig(configUri)
const artifacts = await getArtifactsFromCanonicalConfig(hre, canonicalConfig)

const canonicalConfig = await hre.run(TASK_CHUGSPLASH_FETCH, {
configUri,
})
const artifacts = await getArtifactsFromParsedCanonicalConfig(
hre,
parseChugSplashConfig(canonicalConfig) as CanonicalChugSplashConfig
)
const ChugSplashManager = new Contract(
getChugSplashManagerProxyAddress(canonicalConfig.options.projectName),
ChugSplashManagerABI,
hre.ethers.provider
)

for (const [referenceName, contractConfig] of Object.entries(
canonicalConfig.contracts
)) {
const artifact = artifacts[contractConfig.contract]
const { abi, contractName, sourceName, fileOutput } = artifact
for (const referenceName of Object.keys(canonicalConfig.contracts)) {
const artifact = artifacts[referenceName]
const { abi, contractName, sourceName, compilerOutput } = artifact
const { constructorArgValues } = getConstructorArgs(
canonicalConfig,
referenceName,
abi,
fileOutput,
compilerOutput,
sourceName,
contractName
)

const contractAddress = await ChugSplashManager.implementations(
const implementationAddress = await ChugSplashManager.implementations(
referenceName
)

Expand All @@ -79,7 +83,7 @@ export const verifyChugSplashConfig = async (hre: any, configUri: string) => {
hre.network.name,
hre.network.provider,
etherscanApiEndpoints,
contractAddress,
implementationAddress,
sourceName,
contractName,
abi,
Expand All @@ -88,6 +92,14 @@ export const verifyChugSplashConfig = async (hre: any, configUri: string) => {
compilerInput.solcVersion,
constructorArgValues
)

await linkProxyWithImplementation(
etherscanApiEndpoints,
etherscanApiKey,
getProxyAddress(canonicalConfig.options.projectName, referenceName),
implementationAddress,
contractName
)
}
}

Expand Down
4 changes: 2 additions & 2 deletions packages/executor/src/utils/initialize.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ethers } from 'ethers'
import { deployChugSplashContracts } from '@chugsplash/plugins'
import { deployChugSplashPredeploys } from '@chugsplash/plugins'
import {
CHUGSPLASH_CONSTRUCTOR_ARGS,
ChugSplashBootLoaderArtifact,
Expand Down Expand Up @@ -29,7 +29,7 @@ export const initializeChugSplashContracts = async (
hre: any,
deployer: ethers.Signer
) => {
await deployChugSplashContracts(hre, deployer)
await deployChugSplashPredeploys(hre, deployer)

const { etherscanApiKey, etherscanApiEndpoints } = await getEtherscanInfo(hre)

Expand Down
96 changes: 94 additions & 2 deletions packages/plugins/src/hardhat/artifacts.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import path from 'path'

import * as semver from 'semver'
import { SolidityStorageLayout, ChugSplashConfig } from '@chugsplash/core'
import { remove0x } from '@eth-optimism/core-utils'
import {
SolidityStorageLayout,
ChugSplashConfig,
CanonicalChugSplashConfig,
} from '@chugsplash/core'
import { add0x, remove0x } from '@eth-optimism/core-utils'
import { ethers, utils } from 'ethers'
import {
TASK_COMPILE_SOLIDITY_GET_SOLC_BUILD,
TASK_COMPILE_SOLIDITY_RUN_SOLC,
TASK_COMPILE_SOLIDITY_RUN_SOLCJS,
} from 'hardhat/builtin-tasks/task-names'
import { SolcBuild } from 'hardhat/types'

// TODO
export type ContractArtifact = any
Expand Down Expand Up @@ -291,3 +301,85 @@ export const getImmutableVariables = (
}
return immutableVariables
}

export const getArtifactsFromParsedCanonicalConfig = async (
hre: any,
parsedCanonicalConfig: CanonicalChugSplashConfig
): Promise<{ [referenceName: string]: any }> => {
const compilerOutputs: any[] = []
// Get the compiler output for each compiler input.
for (const compilerInput of parsedCanonicalConfig.inputs) {
const solcBuild: SolcBuild = await hre.run(
TASK_COMPILE_SOLIDITY_GET_SOLC_BUILD,
{
quiet: true,
solcVersion: compilerInput.solcVersion,
}
)

let compilerOutput: any // TODO: Compiler output type
if (solcBuild.isSolcJs) {
compilerOutput = await hre.run(TASK_COMPILE_SOLIDITY_RUN_SOLCJS, {
input: compilerInput.input,
solcJsPath: solcBuild.compilerPath,
})
} else {
compilerOutput = await hre.run(TASK_COMPILE_SOLIDITY_RUN_SOLC, {
input: compilerInput.input,
solcPath: solcBuild.compilerPath,
})
}
compilerOutputs.push(compilerOutput)
}

const artifacts = {}
// Generate an artifact for each contract in the ChugSplash config.
for (const [referenceName, contractConfig] of Object.entries(
parsedCanonicalConfig.contracts
)) {
let compilerOutputIndex = 0
while (artifacts[referenceName] === undefined) {
// Iterate through the sources in the current compiler output to find the one that
// contains this contract.
const compilerOutput = compilerOutputs[compilerOutputIndex]
for (const [sourceName, sourceOutput] of Object.entries(
compilerOutput.contracts
)) {
// Check if the current source contains the contract.
if (sourceOutput.hasOwnProperty(contractConfig.contract)) {
const contractOutput = sourceOutput[contractConfig.contract]

const creationCode = getCreationCode(
add0x(contractOutput.evm.bytecode.object),
parsedCanonicalConfig,
referenceName,
contractOutput.abi,
compilerOutput,
sourceName,
contractConfig.contract
)
const immutableVariables = getImmutableVariables(
compilerOutput,
sourceName,
contractConfig.contract
)

artifacts[referenceName] = {
creationCode,
storageLayout: contractOutput.storageLayout,
immutableVariables,
abi: contractOutput.abi,
compilerOutput,
sourceName,
contractName: contractConfig.contract,
}
// We can exit the loop at this point since each contract only has a single artifact
// associated with it.
break
}
}
compilerOutputIndex += 1
}
}
return artifacts
}
Loading

0 comments on commit 7b33791

Please sign in to comment.