Skip to content

Commit

Permalink
fix(core): write separate artifacts for proxy and implementation cont…
Browse files Browse the repository at this point in the history
…racts
  • Loading branch information
sam-goldman committed Apr 5, 2023
1 parent 295e542 commit 0ef343d
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 151 deletions.
6 changes: 6 additions & 0 deletions .changeset/heavy-bears-hug.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@chugsplash/core': patch
'@chugsplash/plugins': patch
---

Write artifacts for proxy and implementation contracts
196 changes: 122 additions & 74 deletions packages/core/src/actions/artifacts.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { ethers } from 'ethers'
import ora from 'ora'
import {
ProxyABI,
ProxyArtifact,
buildInfo as chugsplashBuildInfo,
} from '@chugsplash/contracts'

import { ParsedChugSplashConfig } from '../config/types'
import {
Expand All @@ -8,11 +12,12 @@ import {
} from '../languages/solidity/types'
import { Integration } from '../constants'
import {
createDeploymentFolderForNetwork,
writeDeploymentFolderForNetwork,
getConstructorArgs,
readBuildInfo,
readContractArtifact,
writeDeploymentArtifact,
getChugSplashManagerAddress,
} from '../utils'

import 'core-js/features/array/at'
Expand Down Expand Up @@ -46,83 +51,126 @@ export const getDeployedBytecode = async (
return deployedBytecode
}

export const createDeploymentArtifacts = async (
provider: ethers.providers.JsonRpcProvider,
export const writeDeploymentArtifacts = async (
provider: ethers.providers.Provider,
parsedConfig: ParsedChugSplashConfig,
finalDeploymentTxnHash: string,
artifactPaths: ArtifactPaths,
integration: Integration,
spinner: ora.Ora,
deploymentEvents: ethers.Event[],
networkName: string,
deploymentFolderPath: string
deploymentFolderPath: string,
artifactPaths: ArtifactPaths,
integration: Integration
) => {
spinner.start(`Writing deployment artifacts...`)

createDeploymentFolderForNetwork(networkName, deploymentFolderPath)

for (const [referenceName, contractConfig] of Object.entries(
parsedConfig.contracts
)) {
const artifact = readContractArtifact(
artifactPaths[referenceName].contractArtifactPath,
integration
)
const { sourceName, contractName, bytecode, abi } = artifact

const buildInfo = readBuildInfo(artifactPaths[referenceName].buildInfoPath)
writeDeploymentFolderForNetwork(networkName, deploymentFolderPath)

const { constructorArgValues } = getConstructorArgs(
parsedConfig.contracts[referenceName].constructorArgs,
referenceName,
abi
)

const receipt = await provider.getTransactionReceipt(finalDeploymentTxnHash)

const metadata =
buildInfo.output.contracts[sourceName][contractName].metadata

const { devdoc, userdoc } =
typeof metadata === 'string'
? JSON.parse(metadata).output
: metadata.output

const deploymentArtifact = {
contractName,
address: contractConfig.proxy,
abi,
transactionHash: finalDeploymentTxnHash,
solcInputHash: buildInfo.id,
receipt: {
...receipt,
gasUsed: receipt.gasUsed.toString(),
cumulativeGasUsed: receipt.cumulativeGasUsed.toString(),
// Exclude the `effectiveGasPrice` if it's undefined, which is the case on Optimism.
...(receipt.effectiveGasPrice && {
effectiveGasPrice: receipt.effectiveGasPrice.toString(),
}),
},
numDeployments: 1,
metadata:
typeof metadata === 'string' ? metadata : JSON.stringify(metadata),
args: constructorArgValues,
bytecode,
deployedBytecode: await provider.getCode(contractConfig.proxy),
devdoc,
userdoc,
storageLayout: readStorageLayout(
artifactPaths[referenceName].buildInfoPath,
contractConfig.contract
),
for (const deploymentEvent of deploymentEvents) {
if (!deploymentEvent.args) {
throw new Error(`Deployment event has no arguments. Should never happen.`)
}

writeDeploymentArtifact(
networkName,
deploymentFolderPath,
deploymentArtifact,
referenceName
)
const receipt = await deploymentEvent.getTransactionReceipt()

if (deploymentEvent.event === 'DefaultProxyDeployed') {
const { metadata, storageLayout } =
chugsplashBuildInfo.output.contracts['contracts/libraries/Proxy.sol'][
'Proxy'
]
const { devdoc, userdoc } =
typeof metadata === 'string'
? JSON.parse(metadata).output
: metadata.output

// Define the deployment artifact for the proxy.
const proxyArtifact = {
address: deploymentEvent.args.proxy,
abi: ProxyABI,
transactionHash: deploymentEvent.transactionHash,
solcInputHash: chugsplashBuildInfo.id,
receipt: {
...receipt,
gasUsed: receipt.gasUsed.toString(),
cumulativeGasUsed: receipt.cumulativeGasUsed.toString(),
// Exclude the `effectiveGasPrice` if it's undefined, which is the case on Optimism.
...(receipt.effectiveGasPrice && {
effectiveGasPrice: receipt.effectiveGasPrice.toString(),
}),
},
numDeployments: 1,
metadata:
typeof metadata === 'string' ? metadata : JSON.stringify(metadata),
args: [
getChugSplashManagerAddress(parsedConfig.options.organizationID),
],
bytecode: ProxyArtifact.bytecode,
deployedBytecode: await provider.getCode(deploymentEvent.args.proxy),
devdoc,
userdoc,
storageLayout,
}

// Write the deployment artifact for the proxy contract.
writeDeploymentArtifact(
networkName,
deploymentFolderPath,
proxyArtifact,
`${deploymentEvent.args.referenceName}Proxy`
)
} else if (deploymentEvent.event === 'ContractDeployed') {
// Get the deployed contract's info.
const referenceName = deploymentEvent.args.referenceName
const artifact = readContractArtifact(
artifactPaths[referenceName].contractArtifactPath,
integration
)
const { sourceName, contractName, bytecode, abi } = artifact
const buildInfo = readBuildInfo(
artifactPaths[referenceName].buildInfoPath
)
const { constructorArgValues } = getConstructorArgs(
parsedConfig.contracts[referenceName].constructorArgs,
referenceName,
abi
)
const { metadata, storageLayout } =
buildInfo.output.contracts[sourceName][contractName]
const { devdoc, userdoc } =
typeof metadata === 'string'
? JSON.parse(metadata).output
: metadata.output

// Define the deployment artifact for the deployed contract.
const contractArtifact = {
address: deploymentEvent.args.contractAddress,
abi,
transactionHash: deploymentEvent.transactionHash,
solcInputHash: buildInfo.id,
receipt: {
...receipt,
gasUsed: receipt.gasUsed.toString(),
cumulativeGasUsed: receipt.cumulativeGasUsed.toString(),
// Exclude the `effectiveGasPrice` if it's undefined, which is the case on Optimism.
...(receipt.effectiveGasPrice && {
effectiveGasPrice: receipt.effectiveGasPrice.toString(),
}),
},
numDeployments: 1,
metadata:
typeof metadata === 'string' ? metadata : JSON.stringify(metadata),
args: constructorArgValues,
bytecode,
deployedBytecode: await provider.getCode(
deploymentEvent.args.contractAddress
),
devdoc,
userdoc,
storageLayout,
}
// Write the deployment artifact for the deployed contract.
writeDeploymentArtifact(
networkName,
deploymentFolderPath,
contractArtifact,
referenceName
)
}
}

spinner.succeed(`Wrote deployment artifacts.`)
}
5 changes: 1 addition & 4 deletions packages/core/src/actions/bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -380,10 +380,7 @@ export const makeActionBundleFromConfig = async (
})
}

// Create an AST Dereferencer. We must convert the CompilerOutput type to `any` here because
// because a type error will be thrown otherwise. Coverting to `any` is harmless because we use
// Hardhat's default `CompilerOutput`, which is what OpenZeppelin expects.
const dereferencer = astDereferencer(compilerOutput as any)
const dereferencer = astDereferencer(compilerOutput)

const extendedLayout = extendStorageLayout(storageLayout, dereferencer)

Expand Down
6 changes: 1 addition & 5 deletions packages/core/src/config/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -937,10 +937,7 @@ const parseContractVariables = (
return {}
}

// Create an AST Dereferencer. We must convert the CompilerOutput type to `any` here because
// because a type error will be thrown otherwise. Coverting to `any` is harmless because we use
// Hardhat's default `CompilerOutput`, which is what OpenZeppelin expects.
const dereferencer = astDereferencer(compilerOutput as any)
const dereferencer = astDereferencer(compilerOutput)
const extendedLayout = extendStorageLayout(storageLayout, dereferencer)

const inputErrors: string[] = []
Expand Down Expand Up @@ -1506,7 +1503,6 @@ export const assertValidContracts = (

const sourceUnit = buildInfo.output.sources[sourceName].ast
const decodeSrc = srcDecoder(buildInfo.input, buildInfo.output)
// TODO: can we remove the `as any` in `astDereferencer` in the other parts of the codebase?
const dereferencer = astDereferencer(buildInfo.output)

// Get the ContractDefinition node for this `contractName`. There should only be one
Expand Down
32 changes: 16 additions & 16 deletions packages/core/src/execution/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ import {
ChugSplashBundles,
ChugSplashBundleState,
ChugSplashBundleStatus,
createDeploymentArtifacts,
writeDeploymentArtifacts,
} from '../actions'
import { ParsedChugSplashConfig } from '../config'
import { EXECUTION_BUFFER_MULTIPLIER, Integration } from '../constants'
import { getAmountToDeposit, getOwnerWithdrawableAmount } from '../fund'
import { ArtifactPaths } from '../languages'
import {
formatEther,
getBundleCompletionTxnHash,
getChugSplashManager,
getDeploymentEvents,
getGasPriceOverrides,
getProjectOwnerAddress,
} from '../utils'
Expand Down Expand Up @@ -116,13 +116,12 @@ export const monitorExecution = async (
if (bundleState.status === ChugSplashBundleStatus.COMPLETED) {
spinner.succeed(`Finished executing ${projectName}.`)
spinner.start(`Retrieving deployment info...`)
// Get the `completeChugSplashBundle` transaction.
const bundleCompletionTxnHash = await getBundleCompletionTxnHash(
const deploymentEvents = await getDeploymentEvents(
ChugSplashManager,
bundleId
)
spinner.succeed('Retrieved deployment info.')
return bundleCompletionTxnHash
return deploymentEvents
} else if (bundleState.status === ChugSplashBundleStatus.CANCELLED) {
spinner.fail(`${projectName} was cancelled.`)
throw new Error(`${projectName} was cancelled.`)
Expand All @@ -138,8 +137,7 @@ export const monitorExecution = async (
*
* @param provider JSON RPC provider corresponding to the current project owner.
* @param parsedConfig Parsed ParsedChugSplashConfig.
* @param finalDeploymentTxnHash Hash of the transaction that completed the deployment. This is the
* call to `completeChugSplashBundle` on the ChugSplashManager.
* @param deploymentEvents Array of `DefaultProxyDeployed` and `ContractDeployed` events
* @param withdraw Boolean that determines if remaining funds in the ChugSplashManager should be
* withdrawn to the project owner.
* @param newProjectOwner Optional address to receive ownership of the project.
Expand All @@ -148,13 +146,12 @@ export const postExecutionActions = async (
provider: ethers.providers.JsonRpcProvider,
signer: ethers.Signer,
parsedConfig: ParsedChugSplashConfig,
finalDeploymentTxnHash: string,
deploymentEvents: ethers.Event[],
withdraw: boolean,
networkName: string,
deploymentfolderPath: string,
deploymentFolderPath: string,
artifactPaths: ArtifactPaths,
integration: Integration,
remoteExecution: boolean,
newProjectOwner?: string,
spinner: ora.Ora = ora({ isSilent: true })
) => {
Expand Down Expand Up @@ -228,14 +225,17 @@ export const postExecutionActions = async (
}
}

await createDeploymentArtifacts(
spinner.start(`Writing deployment artifacts...`)

await writeDeploymentArtifacts(
provider,
parsedConfig,
finalDeploymentTxnHash,
artifactPaths,
integration,
spinner,
deploymentEvents,
networkName,
deploymentfolderPath
deploymentFolderPath,
artifactPaths,
integration
)

spinner.succeed(`Wrote deployment artifacts.`)
}
Loading

0 comments on commit 0ef343d

Please sign in to comment.