From 26ab2ad2e1d5d11248bab43645612d36272d6998 Mon Sep 17 00:00:00 2001 From: Sam Goldman Date: Tue, 28 Feb 2023 23:33:38 -0700 Subject: [PATCH] refactor: get previous storage layout in OpenZeppelin format --- .changeset/two-wombats-watch.md | 7 + packages/core/src/actions/artifacts.ts | 4 +- packages/core/src/actions/bundle.ts | 45 +- packages/core/src/config/types.ts | 15 +- packages/core/src/deployed.ts | 189 --------- .../core/src/languages/solidity/compiler.ts | 123 +----- packages/core/src/languages/solidity/types.ts | 39 +- packages/core/src/tasks/index.ts | 22 +- packages/core/src/utils.ts | 398 +++++++++++++++--- packages/executor/src/utils/etherscan.ts | 2 +- .../plugins/chugsplash/foundry/deploy.t.js | 1 + .../TransparentUpgradableUpgrade.config.ts | 2 +- .../hardhat/UUPSUpgradableUpgrade.config.ts | 2 +- packages/plugins/contracts/Storage.sol | 1 + packages/plugins/package.json | 2 + packages/plugins/src/foundry/index.ts | 3 + packages/plugins/src/hardhat/artifacts.ts | 64 ++- packages/plugins/src/hardhat/deployments.ts | 30 +- packages/plugins/src/hardhat/tasks.ts | 65 +-- packages/plugins/src/hardhat/utils.ts | 11 + packages/plugins/test/Storage.spec.ts | 4 + packages/plugins/test/Transfer.spec.ts | 52 ++- packages/plugins/test/constants.ts | 1 + yarn.lock | 2 +- 24 files changed, 624 insertions(+), 460 deletions(-) create mode 100644 .changeset/two-wombats-watch.md delete mode 100644 packages/core/src/deployed.ts create mode 100644 packages/plugins/src/hardhat/utils.ts diff --git a/.changeset/two-wombats-watch.md b/.changeset/two-wombats-watch.md new file mode 100644 index 000000000..af0bb816c --- /dev/null +++ b/.changeset/two-wombats-watch.md @@ -0,0 +1,7 @@ +--- +'@chugsplash/executor': patch +'@chugsplash/plugins': patch +'@chugsplash/core': patch +--- + +Get previous storage layout using OpenZeppelin's format diff --git a/packages/core/src/actions/artifacts.ts b/packages/core/src/actions/artifacts.ts index 77160c2c9..0ed695063 100644 --- a/packages/core/src/actions/artifacts.ts +++ b/packages/core/src/actions/artifacts.ts @@ -8,13 +8,13 @@ import { } from '../languages/solidity/types' import { Integration } from '../constants' import { - addEnumMembersToStorageLayout, createDeploymentFolderForNetwork, getConstructorArgs, readBuildInfo, readContractArtifact, writeDeploymentArtifact, } from '../utils' + import 'core-js/features/array/at' /** @@ -33,8 +33,6 @@ export const readStorageLayout = ( const [sourceName, contractName] = contractFullyQualifiedName.split(':') const contractOutput = buildInfo.output.contracts[sourceName][contractName] - addEnumMembersToStorageLayout(contractOutput.storageLayout, buildInfo.output) - return contractOutput.storageLayout } diff --git a/packages/core/src/actions/bundle.ts b/packages/core/src/actions/bundle.ts index a029f83f8..d823253f5 100644 --- a/packages/core/src/actions/bundle.ts +++ b/packages/core/src/actions/bundle.ts @@ -2,19 +2,19 @@ import { fromHexString, toHexString } from '@eth-optimism/core-utils' import { ethers, providers } from 'ethers' import MerkleTree from 'merkletreejs' -import { ParsedChugSplashConfig } from '../config/types' +import { + CanonicalConfigArtifacts, + ParsedChugSplashConfig, +} from '../config/types' import { Integration } from '../constants' import { computeStorageSlots } from '../languages/solidity/storage' -import { - ArtifactPaths, - SolidityStorageLayout, -} from '../languages/solidity/types' +import { ArtifactPaths } from '../languages/solidity/types' import { getImplAddress, readContractArtifact, getCreationCodeWithConstructorArgs, + readBuildInfo, } from '../utils' -import { readStorageLayout } from './artifacts' import { ChugSplashAction, ChugSplashActionBundle, @@ -230,16 +230,15 @@ export const bundleLocal = async ( artifactPaths: ArtifactPaths, integration: Integration ): Promise => { - const artifacts = {} + const artifacts: CanonicalConfigArtifacts = {} for (const [referenceName, contractConfig] of Object.entries( parsedConfig.contracts )) { - const storageLayout = readStorageLayout( - artifactPaths[referenceName].buildInfoPath, - contractConfig.contract + const { input, output } = readBuildInfo( + artifactPaths[referenceName].buildInfoPath ) - const { abi, bytecode } = readContractArtifact( + const { abi, bytecode, sourceName, contractName } = readContractArtifact( artifactPaths[referenceName].contractArtifactPath, integration ) @@ -250,8 +249,12 @@ export const bundleLocal = async ( abi ) artifacts[referenceName] = { + compilerInput: input, + compilerOutput: output, creationCodeWithConstructorArgs, - storageLayout, + abi, + sourceName, + contractName, } } @@ -268,19 +271,21 @@ export const bundleLocal = async ( export const makeActionBundleFromConfig = async ( provider: providers.Provider, parsedConfig: ParsedChugSplashConfig, - artifacts: { - [name: string]: { - creationCodeWithConstructorArgs: string - storageLayout: SolidityStorageLayout - } - } + artifacts: CanonicalConfigArtifacts ): Promise => { const actions: ChugSplashAction[] = [] for (const [referenceName, contractConfig] of Object.entries( parsedConfig.contracts )) { - const { storageLayout, creationCodeWithConstructorArgs } = - artifacts[referenceName] + const { + creationCodeWithConstructorArgs, + compilerOutput, + sourceName, + contractName, + } = artifacts[referenceName] + + const storageLayout = + compilerOutput.contracts[sourceName][contractName].storageLayout // Skip adding a `DEPLOY_IMPLEMENTATION` action if the implementation has already been deployed. if ( diff --git a/packages/core/src/config/types.ts b/packages/core/src/config/types.ts index d45826655..2f0682cd6 100644 --- a/packages/core/src/config/types.ts +++ b/packages/core/src/config/types.ts @@ -5,8 +5,10 @@ import { REGISTRY_PROXY_TYPE_HASH, } from '@chugsplash/contracts' import { constants } from 'ethers' +import { Fragment } from 'ethers/lib/utils' +import { CompilerInput } from 'hardhat/types' -import { CompilerInput } from '../languages/solidity/types' +import { CompilerOutput } from '../languages/solidity/types' export const externalProxyTypes = [ 'oz-transparent', @@ -131,3 +133,14 @@ export type ChugSplashInput = { solcLongVersion: string input: CompilerInput } + +export type CanonicalConfigArtifacts = { + [referenceName: string]: { + compilerInput: CompilerInput + compilerOutput: CompilerOutput + creationCodeWithConstructorArgs: string + abi: Array + sourceName: string + contractName: string + } +} diff --git a/packages/core/src/deployed.ts b/packages/core/src/deployed.ts deleted file mode 100644 index dc15c739d..000000000 --- a/packages/core/src/deployed.ts +++ /dev/null @@ -1,189 +0,0 @@ -import { - ChugSplashManagerABI, - ChugSplashRecorderABI, - CHUGSPLASH_RECORDER_ADDRESS, -} from '@chugsplash/contracts' -import { getChainId } from '@eth-optimism/core-utils' -import { Manifest } from '@openzeppelin/upgrades-core' -import { Contract, providers } from 'ethers' - -import { chugsplashFetchSubtask } from './config/fetch' -import { CanonicalChugSplashConfig, UserChugSplashConfig } from './config/types' -import { - ArtifactPaths, - getCanonicalConfigArtifacts, - SolidityStorageLayout, -} from './languages' -import { - callWithTimeout, - getEIP1967ProxyImplementationAddress, - readCanonicalConfig, -} from './utils' -import 'core-js/features/array/at' -import { readStorageLayout } from './actions/artifacts' - -export const getLatestDeployedCanonicalConfig = async ( - provider: providers.Provider, - proxyAddress: string, - remoteExecution: boolean, - canonicalConfigFolderPath: string -): Promise => { - const ChugSplashRecorder = new Contract( - CHUGSPLASH_RECORDER_ADDRESS, - ChugSplashRecorderABI, - provider - ) - - const actionExecutedEvents = await ChugSplashRecorder.queryFilter( - ChugSplashRecorder.filters.EventAnnouncedWithData( - 'ChugSplashActionExecuted', - null, - proxyAddress - ) - ) - - if (actionExecutedEvents.length === 0) { - return undefined - } - - const latestRegistryEvent = actionExecutedEvents.at(-1) - - if (latestRegistryEvent === undefined) { - return undefined - } else if (latestRegistryEvent.args === undefined) { - throw new Error(`ChugSplashActionExecuted event has no args.`) - } - - const ChugSplashManager = new Contract( - latestRegistryEvent.args.manager, - ChugSplashManagerABI, - provider - ) - - const latestExecutionEvent = ( - await ChugSplashManager.queryFilter( - ChugSplashManager.filters.ChugSplashActionExecuted(null, proxyAddress) - ) - ).at(-1) - - if (latestExecutionEvent === undefined) { - throw new Error( - `ChugSplashActionExecuted event detected in registry but not in manager contract` - ) - } else if (latestExecutionEvent.args === undefined) { - throw new Error(`ChugSplashActionExecuted event has no args.`) - } - - const latestProposalEvent = ( - await ChugSplashManager.queryFilter( - ChugSplashManager.filters.ChugSplashBundleProposed( - latestExecutionEvent.args.bundleId - ) - ) - ).at(-1) - - if (latestProposalEvent === undefined) { - throw new Error( - `ChugSplashManager emitted a ChugSplashActionExecuted event but not a ChugSplashBundleProposed event` - ) - } else if (latestProposalEvent.args === undefined) { - throw new Error(`ChugSplashBundleProposed event does not have args`) - } - - if (remoteExecution) { - return callWithTimeout( - chugsplashFetchSubtask({ configUri: latestProposalEvent.args.configUri }), - 30000, - 'Failed to fetch config file from IPFS' - ) - } else { - return readCanonicalConfig( - canonicalConfigFolderPath, - latestProposalEvent.args.configUri - ) - } -} - -/** - * Get the most recent storage layout for a given reference name. The order of priority (from - * highest to lowest) is: - * 1. The storage layout at the path specified by the user via 'previousBuildInfo' - * 2. The latest deployment in the ChugSplash system for the corresponding proxy - * 3. OpenZeppelin's Network File (if the proxy is an OpenZeppelin proxy type) - * - * If we detect a 'previousBuildInfo' path as well as a previous deployment using ChugSplash, we log - * a warning to the user and use the storage layout at 'previousBuildInfo'. - */ -export const getLatestDeployedStorageLayout = async ( - provider: providers.Provider, - referenceName: string, - proxyAddress: string, - userConfig: UserChugSplashConfig, - artifactPaths: ArtifactPaths, - remoteExecution: boolean, - canonicalConfigFolderPath: string -): Promise => { - const deployedCanonicalConfig = await getLatestDeployedCanonicalConfig( - provider, - proxyAddress, - remoteExecution, - canonicalConfigFolderPath - ) - - const userContractConfig = userConfig.contracts[referenceName] - if ( - userContractConfig.previousFullyQualifiedName !== undefined && - userContractConfig.previousBuildInfo !== undefined - ) { - const storageLayout = readStorageLayout( - userContractConfig.previousBuildInfo, - userContractConfig.previousFullyQualifiedName - ) - - if (deployedCanonicalConfig !== undefined) { - console.warn( - '\x1b[33m%s\x1b[0m', // Display message in yellow - `\nUsing the "previousBuildInfo" and "previousFullyQualifiedName" field to get the storage layout for\n` + - `the contract: ${referenceName}. If you'd like to use the storage layout from your most recent\n` + - `ChugSplash deployment instead, please remove these two fields from your ChugSplash file.` - ) - } - - return storageLayout - } - - if (deployedCanonicalConfig !== undefined) { - const deployedCanonicalConfigArtifacts = await getCanonicalConfigArtifacts( - deployedCanonicalConfig - ) - return deployedCanonicalConfigArtifacts[referenceName].storageLayout - } else if ( - userContractConfig.externalProxyType === 'oz-transparent' || - userContractConfig.externalProxyType === 'oz-uups' - ) { - const manifest = new Manifest(await getChainId(provider)) - const currImplAddress = await getEIP1967ProxyImplementationAddress( - provider, - proxyAddress - ) - - const data = await manifest.read() - const versionWithoutMetadata = Object.keys(data.impls).find( - (v) => data.impls[v]?.address === currImplAddress - ) - if (versionWithoutMetadata !== undefined) { - const implDeployment = data.impls[versionWithoutMetadata] - if (implDeployment === undefined) { - throw new Error( - 'Could not retrieve deployment info from OpenZeppelin Upgrades artifact' - ) - } - return implDeployment.layout as unknown as SolidityStorageLayout - } - } - - throw new Error( - `Could not find the previous storage layout for the contract: ${referenceName}. Please include\n` + - `a "previousBuildInfo" and "previousFullyQualifiedName" field for this contract in your ChugSplash file.` - ) -} diff --git a/packages/core/src/languages/solidity/compiler.ts b/packages/core/src/languages/solidity/compiler.ts index f027a3849..87a2c30ac 100644 --- a/packages/core/src/languages/solidity/compiler.ts +++ b/packages/core/src/languages/solidity/compiler.ts @@ -1,11 +1,9 @@ -import { SolcBuild } from 'hardhat/types' +import { CompilerInput, SolcBuild } from 'hardhat/types' import { getCompilersDir } from 'hardhat/internal/util/global-dir' import { CompilerDownloader, CompilerPlatform, } from 'hardhat/internal/solidity/compiler/downloader' -import { Compiler, NativeCompiler } from 'hardhat/internal/solidity/compiler' -import { add0x } from '@eth-optimism/core-utils' import { providers } from 'ethers' import { CanonicalChugSplashConfig } from '../../config/types' @@ -14,16 +12,11 @@ import { makeActionBundleFromConfig, } from '../../actions' import { - CompilerInput, - CompilerOutput, CompilerOutputContracts, CompilerOutputMetadata, CompilerOutputSources, } from './types' -import { - addEnumMembersToStorageLayout, - getCreationCodeWithConstructorArgs, -} from '../../utils' +import { getCanonicalConfigArtifacts } from '../../utils' export const bundleRemoteSubtask = async (args: { provider: providers.Provider @@ -83,82 +76,6 @@ export const getSolcBuild = async (solcVersion: string): Promise => { return wasmCompiler } -// TODO: `CanonicalConfigArtifact` type -export const getCanonicalConfigArtifacts = async ( - canonicalConfig: CanonicalChugSplashConfig -): Promise<{ [referenceName: string]: any }> => { - const compilerOutputs: any[] = [] - // Get the compiler output for each compiler input. - for (const compilerInput of canonicalConfig.inputs) { - const solcBuild: SolcBuild = await getSolcBuild(compilerInput.solcVersion) - let compilerOutput: CompilerOutput - if (solcBuild.isSolcJs) { - const compiler = new Compiler(solcBuild.compilerPath) - compilerOutput = await compiler.compile(compilerInput.input) - } else { - const compiler = new NativeCompiler(solcBuild.compilerPath) - compilerOutput = await compiler.compile(compilerInput.input) - } - - if (compilerOutput.errors) { - const formattedErrorMessages: string[] = [] - compilerOutput.errors.forEach((error) => { - // Ignore warnings thrown by the compiler. - if (error.type.toLowerCase() !== 'warning') { - formattedErrorMessages.push(error.formattedMessage) - } - }) - - if (formattedErrorMessages.length > 0) { - throw new Error( - `Failed to compile. Please report this error to ChugSplash.\n` + - `${formattedErrorMessages}` - ) - } - } - - compilerOutputs.push(compilerOutput) - } - - const artifacts = {} - // Generate an artifact for each contract in the ChugSplash config. - for (const [referenceName, contractConfig] of Object.entries( - canonicalConfig.contracts - )) { - // Split the contract's fully qualified name into its source name and contract name. - const [sourceName, contractName] = contractConfig.contract.split(':') - - for (const compilerOutput of compilerOutputs) { - const contractOutput = - compilerOutput.contracts?.[sourceName]?.[contractName] - if (contractOutput !== undefined) { - const creationCodeWithConstructorArgs = - getCreationCodeWithConstructorArgs( - add0x(contractOutput.evm.bytecode.object), - contractConfig.constructorArgs, - referenceName, - contractOutput.abi - ) - - addEnumMembersToStorageLayout( - contractOutput.storageLayout, - compilerOutput - ) - - artifacts[referenceName] = { - creationCodeWithConstructorArgs, - storageLayout: contractOutput.storageLayout, - abi: contractOutput.abi, - compilerOutput, - sourceName, - contractName, - } - } - } - } - return artifacts -} - /** * Returns the minimum compiler input necessary to compile a given source name. All contracts that * are imported in the given source must be included in the minimum compiler input. @@ -215,22 +132,26 @@ export const getMinimumSourceNames = ( // included in the list of minimum source names for the given source. const exportedSymbols = fullOutputSources[sourceName].ast.exportedSymbols - for (const astIds of Object.values(exportedSymbols)) { - if (astIds.length > 1) { - throw new Error( - `Detected more than one AST ID for: ${sourceName}. Please report this error.` - ) - } - const astId = astIds[0] - const nextSourceName = contractAstIdsToSourceNames[astId] - if (!minimumSourceNames.includes(nextSourceName)) { - minimumSourceNames.push(nextSourceName) - minimumSourceNames = getMinimumSourceNames( - nextSourceName, - fullOutputSources, - contractAstIdsToSourceNames, - minimumSourceNames - ) + if (exportedSymbols) { + for (const astIds of Object.values(exportedSymbols)) { + if (astIds === undefined) { + continue + } else if (astIds.length > 1) { + throw new Error( + `Detected more than one AST ID for: ${sourceName}. Please report this error.` + ) + } + const astId = astIds[0] + const nextSourceName = contractAstIdsToSourceNames[astId] + if (!minimumSourceNames.includes(nextSourceName)) { + minimumSourceNames.push(nextSourceName) + minimumSourceNames = getMinimumSourceNames( + nextSourceName, + fullOutputSources, + contractAstIdsToSourceNames, + minimumSourceNames + ) + } } } return minimumSourceNames diff --git a/packages/core/src/languages/solidity/types.ts b/packages/core/src/languages/solidity/types.ts index 28b8e20b3..17dd4f755 100644 --- a/packages/core/src/languages/solidity/types.ts +++ b/packages/core/src/languages/solidity/types.ts @@ -1,4 +1,6 @@ import { Fragment } from 'ethers/lib/utils' +import { CompilerInput } from 'hardhat/types' +import { SourceUnit } from 'solidity-ast' /** * Represents the JSON objects outputted by the Solidity compiler that describe the structure of @@ -56,28 +58,15 @@ export interface StorageSlotSegment { val: string } -export interface CompilerInput { - language: string - sources: { [sourceName: string]: { content: string } } - settings: { - optimizer: { runs?: number; enabled?: boolean } - metadata?: { useLiteralContent: boolean } - outputSelection: { - [sourceName: string]: { - [contractName: string]: string[] - } - } - evmVersion?: string - libraries?: { - [libraryFileName: string]: { - [libraryName: string]: string - } - } - } +export type BuildInfo = { + id: string + solcVersion: string + solcLongVersion: string + input: CompilerInput + output: CompilerOutput } // TODO -export type BuildInfo = any export type ContractASTNode = any export type ContractArtifact = { @@ -95,10 +84,12 @@ export interface CompilerOutputMetadata { urls: string[] } } + output: any } export interface CompilerOutputContract { - abi: any + abi: Array + storageLayout: SolidityStorageLayout evm: { bytecode: CompilerOutputBytecode deployedBytecode: CompilerOutputBytecode @@ -106,7 +97,7 @@ export interface CompilerOutputContract { [methodSignature: string]: string } } - metadata?: string | CompilerOutputMetadata + metadata: string | CompilerOutputMetadata } export interface CompilerOutputContracts { @@ -123,11 +114,7 @@ export interface CompilerOutput { export interface CompilerOutputSource { id: number - ast: { - id: number - exportedSymbols: { [contractName: string]: number[] } - nodes?: any - } + ast: SourceUnit } export interface CompilerOutputSources { diff --git a/packages/core/src/tasks/index.ts b/packages/core/src/tasks/index.ts index 3f660d0dd..c6a56d97f 100644 --- a/packages/core/src/tasks/index.ts +++ b/packages/core/src/tasks/index.ts @@ -5,6 +5,7 @@ import ora from 'ora' import Hash from 'ipfs-only-hash' import { create } from 'ipfs-http-client' import { ProxyABI } from '@chugsplash/contracts' +import { StorageLayout } from '@openzeppelin/upgrades-core' import { CanonicalChugSplashConfig, @@ -12,6 +13,7 @@ import { ParsedChugSplashConfig, readParsedChugSplashConfig, readUserChugSplashConfig, + UserChugSplashConfig, verifyBundle, } from '../config' import { @@ -116,6 +118,7 @@ export const chugsplashProposeAbstractTask = async ( provider: ethers.providers.JsonRpcProvider, signer: ethers.Signer, parsedConfig: ParsedChugSplashConfig, + userConfig: UserChugSplashConfig, configPath: string, ipfsUrl: string, silent: boolean, @@ -125,6 +128,9 @@ export const chugsplashProposeAbstractTask = async ( artifactPaths: ArtifactPaths, canonicalConfigPath: string, skipStorageCheck: boolean, + openzeppelinStorageLayouts?: { + [referenceName: string]: StorageLayout + }, stream: NodeJS.WritableStream = process.stderr ) => { const spinner = ora({ isSilent: silent, stream }) @@ -134,18 +140,17 @@ export const chugsplashProposeAbstractTask = async ( assertValidContracts(parsedConfig, artifactPaths) - const userConfig = readUserChugSplashConfig(configPath) await assertValidParsedChugSplashFile( provider, parsedConfig, userConfig, artifactPaths, integration, - remoteExecution, canonicalConfigPath, - skipStorageCheck, + remoteExecution, confirm, - spinner + spinner, + openzeppelinStorageLayouts ) if ( @@ -657,6 +662,9 @@ export const chugsplashDeployAbstractTask = async ( integration: Integration, skipStorageCheck: boolean, executor?: ChugSplashExecutorType, + openzeppelinStorageLayouts?: { + [referenceName: string]: StorageLayout + }, stream: NodeJS.WritableStream = process.stderr ): Promise => { const spinner = ora({ isSilent: silent, stream }) @@ -697,11 +705,11 @@ export const chugsplashDeployAbstractTask = async ( userConfig, artifactPaths, integration, - remoteExecution, canonicalConfigPath, - skipStorageCheck, + remoteExecution, confirm, - spinner + spinner, + openzeppelinStorageLayouts ) if (projectPreviouslyRegistered === false) { diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index 6613862d3..bba4fae5d 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -27,41 +27,50 @@ import { ChugSplashRecorderABI, } from '@chugsplash/contracts' import { TransactionRequest } from '@ethersproject/abstract-provider' -import { remove0x } from '@eth-optimism/core-utils' +import { add0x, remove0x } from '@eth-optimism/core-utils' import yesno from 'yesno' import ora from 'ora' -import { assertStorageUpgradeSafe } from '@openzeppelin/upgrades-core' -import { astDereferencer } from 'solidity-ast/utils' +import { + assertStorageUpgradeSafe, + ProxyDeployment, + StorageLayout, + UpgradeableContract, + ValidationOptions, +} from '@openzeppelin/upgrades-core' +import { ContractDefinition } from 'solidity-ast' +import { CompilerInput, SolcBuild } from 'hardhat/types' +import { Compiler, NativeCompiler } from 'hardhat/internal/solidity/compiler' import { CanonicalChugSplashConfig, + CanonicalConfigArtifacts, ExternalProxyType, externalProxyTypes, ParsedChugSplashConfig, ParsedConfigVariable, ParsedConfigVariables, + ParsedContractConfig, ParsedContractConfigs, + ProxyType, proxyTypeHashes, UserChugSplashConfig, UserConfigVariable, + UserContractConfig, } from './config/types' -import { - ChugSplashActionBundle, - ChugSplashActionType, - readStorageLayout, -} from './actions' +import { ChugSplashActionBundle, ChugSplashActionType } from './actions' import { Integration, keywords } from './constants' import 'core-js/features/array/at' -import { getLatestDeployedStorageLayout } from './deployed' import { FoundryContractArtifact } from './types' import { ArtifactPaths, - BuildInfo, ContractArtifact, ContractASTNode, CompilerOutputSources, - SolidityStorageLayout, + BuildInfo, + CompilerOutput, } from './languages/solidity/types' +import { chugsplashFetchSubtask } from './config' +import { getSolcBuild } from './languages' export const computeBundleId = ( bundleRoot: string, @@ -658,11 +667,13 @@ export const assertValidParsedChugSplashFile = async ( userConfig: UserChugSplashConfig, artifactPaths: ArtifactPaths, integration: Integration, - remoteExecution: boolean, canonicalConfigFolderPath: string, - skipStorageCheck: boolean, + remoteExecution: boolean, confirm: boolean, - spinner?: ora.Ora + spinner?: ora.Ora, + openzeppelinStorageLayouts?: { + [referenceName: string]: StorageLayout + } ) => { // Determine if the deployment is an upgrade const projectName = parsedConfig.options.projectName @@ -761,18 +772,25 @@ permission to call the 'upgradeTo' function on each of them. const isProxyDeployed = (await provider.getCode(contractConfig.proxy)) !== '0x' if (isProxyDeployed) { - const currStorageLayout = await getLatestDeployedStorageLayout( + const userContractConfig = userConfig.contracts[referenceName] + const previousStorageLayout = await getPreviousStorageLayoutOZFormat( provider, referenceName, - contractConfig.proxy, - userConfig, - artifactPaths, + contractConfig, + userContractConfig, remoteExecution, - canonicalConfigFolderPath + canonicalConfigFolderPath, + openzeppelinStorageLayouts + ) + + const { input, output } = readBuildInfo( + artifactPaths[referenceName].buildInfoPath ) - const newStorageLayout = readStorageLayout( - artifactPaths[referenceName].buildInfoPath, - contractConfig.contract + const newStorageLayout = getOpenZeppelinStorageLayout( + contractConfig.contract, + input, + output, + contractConfig.proxyType ) // We could check for the `skipStorageCheck` in the outer for-loop, but this makes it easy to @@ -780,9 +798,9 @@ permission to call the 'upgradeTo' function on each of them. if (parsedConfig.options.skipStorageCheck !== true) { // Run OpenZeppelin's storage slot checker. assertStorageUpgradeSafe( - currStorageLayout as any, - newStorageLayout as any, - false + previousStorageLayout, + newStorageLayout, + getOpenZeppelinValidationOpts(contractConfig.proxyType) ) } } @@ -968,10 +986,16 @@ export const assertValidContracts = ( const outputSource = buildInfo.output.sources[sourceName] const childContractNode = outputSource.ast.nodes.find( - (node) => + (node): node is ContractDefinition => node.nodeType === 'ContractDefinition' && node.name === contractName ) + if (childContractNode === undefined) { + throw new Error( + `Could not find the child contract node for "${referenceName}"` + ) + } + const parentContractNodeAstIds = childContractNode.linearizedBaseContracts.filter( (astId) => astId !== childContractNode.id @@ -1186,34 +1210,6 @@ export const readBuildInfo = (buildInfoPath: string): BuildInfo => { return buildInfo } -export const addEnumMembersToStorageLayout = ( - storageLayout: SolidityStorageLayout, - outputSources: any -): SolidityStorageLayout => { - // If no vars are defined or all vars are immutable, then storageLayout.types will be null and we can just return - if (storageLayout.types === null) { - return storageLayout - } - - const deref = astDereferencer(outputSources) - - for (const [typeName, typeDefinition] of Object.entries( - storageLayout.types - )) { - if (typeDefinition.label.startsWith('enum')) { - const astId = typeName.split(')').at(-1) - if (!astId) { - throw new Error(`Could not find AST ID for variable: ${typeName}`) - } - const enumDefinition = deref('EnumDefinition', parseInt(astId, 10)) - typeDefinition.members = enumDefinition.members.map( - (member) => member.name - ) - } - } - return storageLayout -} - /** * Retrieves artifact info from foundry artifacts and returns it in hardhat compatible format. * @@ -1352,3 +1348,297 @@ export const callWithTimeout = async ( return result }) } + +export const toOpenZeppelinProxyType = ( + proxyType: ProxyType +): ProxyDeployment['kind'] => { + if ( + proxyType === 'internal-default' || + proxyType === 'external-default' || + proxyType === 'oz-transparent' || + proxyType === 'internal-registry' + ) { + return 'transparent' + } else if (proxyType === 'oz-uups') { + return 'uups' + } else { + throw new Error( + `Attempted to convert "${proxyType}" to an OpenZeppelin proxy type` + ) + } +} + +export const getOpenZeppelinValidationOpts = ( + proxyType: ProxyType +): Required => { + return { + kind: toOpenZeppelinProxyType(proxyType), + unsafeAllow: [], + unsafeAllowCustomTypes: false, + unsafeAllowLinkedLibraries: false, + unsafeAllowRenames: false, + unsafeSkipStorageCheck: false, + } +} + +export const getOpenZeppelinStorageLayout = ( + fullyQualifiedName: string, + compilerInput: CompilerInput, + compilerOutput: CompilerOutput, + proxyType: ProxyType +): StorageLayout => { + const contract = new UpgradeableContract( + fullyQualifiedName, + compilerInput, + // Without converting the `compilerOutput` type to `any`, OpenZeppelin throws an error due + // to the `SolidityStorageLayout` type that we've added to Hardhat's `CompilerOutput` type. + // Converting this type to `any` shouldn't impact anything since we use Hardhat's default + // `CompilerOutput`, which is what OpenZeppelin expects. + compilerOutput as any, + getOpenZeppelinValidationOpts(proxyType) + ) + + return contract.layout +} + +/** + * Get the most recent storage layout for the given reference name. Uses OpenZeppelin's + * StorageLayout format for consistency. + * + * When retrieving the storage layout, this function uses the following order of priority (from + * highest to lowest): + * 1. The 'previousBuildInfo' and 'previousFullyQualifiedName' fields if both have been declared by + * the user. + * 2. The latest deployment in the ChugSplash system for the proxy address that corresponds to the + * reference name. + * 3. OpenZeppelin's Network File if the proxy is an OpenZeppelin proxy type + * + * If (1) and (2) above are both satisfied, we log a warning to the user and default to using the + * storage layout located at 'previousBuildInfo'. + */ +export const getPreviousStorageLayoutOZFormat = async ( + provider: providers.Provider, + referenceName: string, + parsedContractConfig: ParsedContractConfig, + userContractConfig: UserContractConfig, + remoteExecution: boolean, + canonicalConfigFolderPath: string, + openzeppelinStorageLayouts?: { + [referenceName: string]: StorageLayout + } +): Promise => { + if ((await provider.getCode(parsedContractConfig.proxy)) === '0x') { + throw new Error( + `Proxy has not been deployed for the contract: ${referenceName}.` + ) + } + + const previousCanonicalConfig = await getPreviousCanonicalConfig( + provider, + parsedContractConfig.proxy, + remoteExecution, + canonicalConfigFolderPath + ) + + if ( + userContractConfig.previousFullyQualifiedName !== undefined && + userContractConfig.previousBuildInfo !== undefined + ) { + const { input, output } = readBuildInfo( + userContractConfig.previousBuildInfo + ) + + if (previousCanonicalConfig !== undefined) { + console.warn( + '\x1b[33m%s\x1b[0m', // Display message in yellow + `\nUsing the "previousBuildInfo" and "previousFullyQualifiedName" field to get the storage layout for\n` + + `the contract: ${referenceName}. If you'd like to use the storage layout from your most recent\n` + + `ChugSplash deployment instead, please remove these two fields from your ChugSplash file.` + ) + } + + return getOpenZeppelinStorageLayout( + userContractConfig.previousFullyQualifiedName, + input, + output, + parsedContractConfig.proxyType + ) + } else if (previousCanonicalConfig !== undefined) { + const prevCanonicalConfigArtifacts = await getCanonicalConfigArtifacts( + previousCanonicalConfig + ) + const { sourceName, contractName, compilerInput, compilerOutput } = + prevCanonicalConfigArtifacts[referenceName] + return getOpenZeppelinStorageLayout( + `${sourceName}:${contractName}`, + compilerInput, + compilerOutput, + parsedContractConfig.proxyType + ) + } else if (openzeppelinStorageLayouts?.[referenceName] !== undefined) { + return openzeppelinStorageLayouts[referenceName] + } else { + throw new Error( + `Could not find the previous storage layout for the contract: ${referenceName}. Please include\n` + + `a "previousBuildInfo" and "previousFullyQualifiedName" field for this contract in your ChugSplash file.` + ) + } +} + +export const getPreviousCanonicalConfig = async ( + provider: providers.Provider, + proxyAddress: string, + remoteExecution: boolean, + canonicalConfigFolderPath: string +): Promise => { + const ChugSplashRecorder = new Contract( + CHUGSPLASH_RECORDER_ADDRESS, + ChugSplashRecorderABI, + provider + ) + + const actionExecutedEvents = await ChugSplashRecorder.queryFilter( + ChugSplashRecorder.filters.EventAnnouncedWithData( + 'ChugSplashActionExecuted', + null, + proxyAddress + ) + ) + + if (actionExecutedEvents.length === 0) { + return undefined + } + + const latestRegistryEvent = actionExecutedEvents.at(-1) + + if (latestRegistryEvent === undefined) { + return undefined + } else if (latestRegistryEvent.args === undefined) { + throw new Error(`ChugSplashActionExecuted event has no args.`) + } + + const ChugSplashManager = new Contract( + latestRegistryEvent.args.manager, + ChugSplashManagerABI, + provider + ) + + const latestExecutionEvent = ( + await ChugSplashManager.queryFilter( + ChugSplashManager.filters.ChugSplashActionExecuted(null, proxyAddress) + ) + ).at(-1) + + if (latestExecutionEvent === undefined) { + throw new Error( + `ChugSplashActionExecuted event detected in registry but not in manager contract` + ) + } else if (latestExecutionEvent.args === undefined) { + throw new Error(`ChugSplashActionExecuted event has no args.`) + } + + const latestProposalEvent = ( + await ChugSplashManager.queryFilter( + ChugSplashManager.filters.ChugSplashBundleProposed( + latestExecutionEvent.args.bundleId + ) + ) + ).at(-1) + + if (latestProposalEvent === undefined) { + throw new Error( + `ChugSplashManager emitted a ChugSplashActionExecuted event but not a ChugSplashBundleProposed event` + ) + } else if (latestProposalEvent.args === undefined) { + throw new Error(`ChugSplashBundleProposed event does not have args`) + } + + if (remoteExecution) { + return callWithTimeout( + chugsplashFetchSubtask({ configUri: latestProposalEvent.args.configUri }), + 30000, + 'Failed to fetch config file from IPFS' + ) + } else { + return readCanonicalConfig( + canonicalConfigFolderPath, + latestProposalEvent.args.configUri + ) + } +} + +export const getCanonicalConfigArtifacts = async ( + canonicalConfig: CanonicalChugSplashConfig +): Promise => { + const solcArray: { + compilerInput: CompilerInput + compilerOutput: CompilerOutput + }[] = [] + // Get the compiler output for each compiler input. + for (const chugsplashInput of canonicalConfig.inputs) { + const solcBuild: SolcBuild = await getSolcBuild(chugsplashInput.solcVersion) + let compilerOutput: CompilerOutput + if (solcBuild.isSolcJs) { + const compiler = new Compiler(solcBuild.compilerPath) + compilerOutput = await compiler.compile(chugsplashInput.input) + } else { + const compiler = new NativeCompiler(solcBuild.compilerPath) + compilerOutput = await compiler.compile(chugsplashInput.input) + } + + if (compilerOutput.errors) { + const formattedErrorMessages: string[] = [] + compilerOutput.errors.forEach((error) => { + // Ignore warnings thrown by the compiler. + if (error.type.toLowerCase() !== 'warning') { + formattedErrorMessages.push(error.formattedMessage) + } + }) + + if (formattedErrorMessages.length > 0) { + throw new Error( + `Failed to compile. Please report this error to ChugSplash.\n` + + `${formattedErrorMessages}` + ) + } + } + + solcArray.push({ + compilerInput: chugsplashInput.input, + compilerOutput, + }) + } + + const artifacts: CanonicalConfigArtifacts = {} + // Generate an artifact for each contract in the ChugSplash config. + for (const [referenceName, contractConfig] of Object.entries( + canonicalConfig.contracts + )) { + // Split the contract's fully qualified name into its source name and contract name. + const [sourceName, contractName] = contractConfig.contract.split(':') + + for (const { compilerInput, compilerOutput } of solcArray) { + const contractOutput = + compilerOutput.contracts?.[sourceName]?.[contractName] + if (contractOutput !== undefined) { + const creationCodeWithConstructorArgs = + getCreationCodeWithConstructorArgs( + add0x(contractOutput.evm.bytecode.object), + contractConfig.constructorArgs, + referenceName, + contractOutput.abi + ) + + artifacts[referenceName] = { + creationCodeWithConstructorArgs, + abi: contractOutput.abi, + compilerInput, + compilerOutput, + sourceName, + contractName, + } + } + } + } + return artifacts +} diff --git a/packages/executor/src/utils/etherscan.ts b/packages/executor/src/utils/etherscan.ts index a42163999..94e4a9ebf 100644 --- a/packages/executor/src/utils/etherscan.ts +++ b/packages/executor/src/utils/etherscan.ts @@ -2,7 +2,6 @@ import assert from 'assert' import { ethers } from 'ethers' import { - CompilerInput, getChugSplashManagerProxyAddress, getConstructorArgs, chugsplashFetchSubtask, @@ -59,6 +58,7 @@ import { OZ_UUPS_ADAPTER_ADDRESS, } from '@chugsplash/contracts' import { request } from 'undici' +import { CompilerInput } from 'hardhat/types' import { etherscanApiKey as apiKey, customChains } from './constants' diff --git a/packages/plugins/chugsplash/foundry/deploy.t.js b/packages/plugins/chugsplash/foundry/deploy.t.js index 1a3cdc2d6..341d4c16f 100644 --- a/packages/plugins/chugsplash/foundry/deploy.t.js +++ b/packages/plugins/chugsplash/foundry/deploy.t.js @@ -9,6 +9,7 @@ const variables = { longStringTest: 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz', bytesTest: '0xabcd1234', + bytes32Test: '0x' + '11'.repeat(32), longBytesTest: '0x123456789101112131415161718192021222324252627282930313233343536373839404142434445464', contractTest: '0x' + '11'.repeat(20), diff --git a/packages/plugins/chugsplash/hardhat/TransparentUpgradableUpgrade.config.ts b/packages/plugins/chugsplash/hardhat/TransparentUpgradableUpgrade.config.ts index 7a50e6138..cc8e4820a 100644 --- a/packages/plugins/chugsplash/hardhat/TransparentUpgradableUpgrade.config.ts +++ b/packages/plugins/chugsplash/hardhat/TransparentUpgradableUpgrade.config.ts @@ -22,7 +22,7 @@ const config: UserChugSplashConfig = { // don't create the Network file in the `.openzeppelin/` folder anymore: // https://docs.openzeppelin.com/upgrades-plugins/1.x/network-files#temporary-files previousBuildInfo: - 'artifacts/build-info/642a65c7994e24b06b245ad973ffe592.json', + 'artifacts/build-info/9be231364fa551f736fe8fa2e63af298.json', previousFullyQualifiedName: 'contracts/TransparentUpgradableV1.sol:TransparentUpgradableV1', }, diff --git a/packages/plugins/chugsplash/hardhat/UUPSUpgradableUpgrade.config.ts b/packages/plugins/chugsplash/hardhat/UUPSUpgradableUpgrade.config.ts index 3ad331641..1eedb589e 100644 --- a/packages/plugins/chugsplash/hardhat/UUPSUpgradableUpgrade.config.ts +++ b/packages/plugins/chugsplash/hardhat/UUPSUpgradableUpgrade.config.ts @@ -22,7 +22,7 @@ const config: UserChugSplashConfig = { // don't create the Network file in the `.openzeppelin/` folder anymore: // https://docs.openzeppelin.com/upgrades-plugins/1.x/network-files#temporary-files previousBuildInfo: - 'artifacts/build-info/642a65c7994e24b06b245ad973ffe592.json', + 'artifacts/build-info/9be231364fa551f736fe8fa2e63af298.json', previousFullyQualifiedName: 'contracts/UUPSUpgradableV1.sol:UUPSUpgradableV1', }, diff --git a/packages/plugins/contracts/Storage.sol b/packages/plugins/contracts/Storage.sol index 81fee8579..839888696 100644 --- a/packages/plugins/contracts/Storage.sol +++ b/packages/plugins/contracts/Storage.sol @@ -17,6 +17,7 @@ contract Storage { string public longStringTest; bytes public bytesTest; bytes public longBytesTest; + bytes32 public bytes32Test; Storage public contractTest; TestEnum public enumTest; SimpleStruct public simpleStruct; diff --git a/packages/plugins/package.json b/packages/plugins/package.json index 20307917f..29a0cd127 100644 --- a/packages/plugins/package.json +++ b/packages/plugins/package.json @@ -39,6 +39,8 @@ "@ethereumjs/common": "^3.0.1", "@ethereumjs/vm": "^6.2.0", "@nomiclabs/hardhat-ethers": "^2.2.1", + "@openzeppelin/hardhat-upgrades": "^1.22.1", + "@openzeppelin/upgrades-core": "^1.24.0", "dotenv": "^16.0.3", "ethers": "^5.6.9", "ipfs-http-client": "56.0.3", diff --git a/packages/plugins/src/foundry/index.ts b/packages/plugins/src/foundry/index.ts index 26adaeebf..168f13725 100644 --- a/packages/plugins/src/foundry/index.ts +++ b/packages/plugins/src/foundry/index.ts @@ -120,6 +120,7 @@ const command = args[0] provider, wallet, config, + userConfig, configPath, ipfsUrl, silent, @@ -129,6 +130,7 @@ const command = args[0] artifactPaths, canonicalConfigPath, skipStorageCheck, + undefined, process.stdout ) break @@ -304,6 +306,7 @@ const command = args[0] 'foundry', skipStorageCheck, executor, + undefined, logWriter ) diff --git a/packages/plugins/src/hardhat/artifacts.ts b/packages/plugins/src/hardhat/artifacts.ts index 39455fbcc..40e4f255c 100644 --- a/packages/plugins/src/hardhat/artifacts.ts +++ b/packages/plugins/src/hardhat/artifacts.ts @@ -1,7 +1,21 @@ import path from 'path' -import { BuildInfo, HardhatRuntimeEnvironment } from 'hardhat/types' -import { ArtifactPaths, UserContractConfigs } from '@chugsplash/core' +import { HardhatRuntimeEnvironment } from 'hardhat/types' +import { + ArtifactPaths, + ParsedChugSplashConfig, + UserChugSplashConfig, + UserContractConfigs, + getEIP1967ProxyImplementationAddress, + getOpenZeppelinValidationOpts, + BuildInfo, +} from '@chugsplash/core' +import { + Manifest, + getStorageLayoutForAddress, + StorageLayout, +} from '@openzeppelin/upgrades-core' +import { getDeployData } from '@openzeppelin/hardhat-upgrades/dist/utils/deploy-impl' /** * Retrieves contract build info by name. @@ -15,7 +29,7 @@ export const getBuildInfo = async ( sourceName: string, contractName: string ): Promise => { - let buildInfo: BuildInfo | undefined + let buildInfo try { buildInfo = await hre.artifacts.getBuildInfo( `${sourceName}:${contractName}` @@ -80,3 +94,47 @@ export const getArtifactPaths = async ( } return artifactPaths } + +/** + * Get storage layouts from OpenZeppelin's Network Files for any proxies that are being imported + * into ChugSplash from the OpenZeppelin Hardhat Upgrades plugin. + */ +export const importOpenZeppelinStorageLayouts = async ( + hre: HardhatRuntimeEnvironment, + parsedConfig: ParsedChugSplashConfig, + userConfig: UserChugSplashConfig +): Promise<{ [referenceName: string]: StorageLayout }> => { + const layouts: { [referenceName: string]: StorageLayout } = {} + + for (const [referenceName, parsedContractConfig] of Object.entries( + parsedConfig.contracts + )) { + const isProxyDeployed = await hre.ethers.provider.getCode( + parsedContractConfig.proxy + ) + const { externalProxyType } = userConfig.contracts[referenceName] + if ( + isProxyDeployed && + (externalProxyType === 'oz-transparent' || + externalProxyType === 'oz-uups') + ) { + const manifest = await Manifest.forNetwork(hre.network.provider) + const deployData = await getDeployData( + hre, + await hre.ethers.getContractFactory(parsedContractConfig.contract), + getOpenZeppelinValidationOpts(parsedContractConfig.proxyType) + ) + const storageLayout = await getStorageLayoutForAddress( + manifest, + deployData.validations, + await getEIP1967ProxyImplementationAddress( + hre.ethers.provider, + parsedContractConfig.proxy + ) + ) + layouts[referenceName] = storageLayout + } + } + + return layouts +} diff --git a/packages/plugins/src/hardhat/deployments.ts b/packages/plugins/src/hardhat/deployments.ts index b4b76f5e6..f2c06ef3f 100644 --- a/packages/plugins/src/hardhat/deployments.ts +++ b/packages/plugins/src/hardhat/deployments.ts @@ -13,11 +13,12 @@ import { readUserChugSplashConfig, UserChugSplashConfig, getDefaultProxyAddress, + readParsedChugSplashConfig, } from '@chugsplash/core' -import { getChainId } from '@eth-optimism/core-utils' import { HardhatRuntimeEnvironment } from 'hardhat/types' -import { getArtifactPaths } from './artifacts' +import { getArtifactPaths, importOpenZeppelinStorageLayouts } from './artifacts' +import { isRemoteExecution } from './utils' export const fetchFilesRecursively = (dir): string[] => { const paths: string[] = [] @@ -49,9 +50,7 @@ export const deployAllChugSplashConfigs = async ( confirm: boolean, fileNames?: string[] ) => { - const remoteExecution = - (await getChainId(hre.ethers.provider)) !== - hre.config.networks.hardhat.chainId + const remoteExecution = await isRemoteExecution(hre) fileNames = fileNames ?? (await fetchFilesRecursively(hre.config.paths.chugsplash)) @@ -77,6 +76,19 @@ export const deployAllChugSplashConfigs = async ( path.join(hre.config.paths.artifacts, 'build-info') ) + const parsedConfig = await readParsedChugSplashConfig( + hre.ethers.provider, + configPath, + artifactPaths, + 'hardhat' + ) + + const openzeppelinStorageLayouts = await importOpenZeppelinStorageLayouts( + hre, + parsedConfig, + userConfig + ) + const signer = hre.ethers.provider.getSigner() await chugsplashDeployAbstractTask( hre.ethers.provider, @@ -94,7 +106,8 @@ export const deployAllChugSplashConfigs = async ( deploymentFolder, 'hardhat', true, - executor + executor, + openzeppelinStorageLayouts ) } } @@ -104,10 +117,7 @@ export const getContract = async ( projectName: string, referenceName: string ): Promise => { - if ( - (await getChainId(hre.ethers.provider)) !== - hre.config.networks.hardhat.chainId - ) { + if (await isRemoteExecution(hre)) { throw new Error('Only the Hardhat Network is currently supported.') } const userConfigs: UserChugSplashConfig[] = fetchFilesRecursively( diff --git a/packages/plugins/src/hardhat/tasks.ts b/packages/plugins/src/hardhat/tasks.ts index 2a7d66035..699e0dfd7 100644 --- a/packages/plugins/src/hardhat/tasks.ts +++ b/packages/plugins/src/hardhat/tasks.ts @@ -9,7 +9,6 @@ import { TASK_RUN, TASK_COMPILE, } from 'hardhat/builtin-tasks/task-names' -import { getChainId } from '@eth-optimism/core-utils' import { ParsedChugSplashConfig, ChugSplashActionBundle, @@ -56,7 +55,8 @@ import { sampleTestFileJavaScript, sampleTestFileTypeScript, } from '../sample-project/sample-tests' -import { getArtifactPaths } from './artifacts' +import { getArtifactPaths, importOpenZeppelinStorageLayouts } from './artifacts' +import { isRemoteExecution } from './utils' // Load environment variables from .env dotenv.config() @@ -155,8 +155,7 @@ export const chugsplashDeployTask = async ( const signerAddress = await signer.getAddress() await initializeChugSplash(hre.ethers.provider, signer, [signerAddress]) - const remoteExecution = - (await getChainId(provider)) !== hre.config.networks.hardhat.chainId + const remoteExecution = await isRemoteExecution(hre) let executor: ChugSplashExecutorType | undefined if (remoteExecution) { @@ -180,6 +179,19 @@ export const chugsplashDeployTask = async ( path.join(hre.config.paths.artifacts, 'build-info') ) + const parsedConfig = await readParsedChugSplashConfig( + hre.ethers.provider, + configPath, + artifactPaths, + 'hardhat' + ) + + const openzeppelinStorageLayouts = await importOpenZeppelinStorageLayouts( + hre, + parsedConfig, + userConfig + ) + await chugsplashDeployAbstractTask( provider, signer, @@ -196,7 +208,8 @@ export const chugsplashDeployTask = async ( deploymentFolder, 'hardhat', skipStorageCheck, - executor + executor, + openzeppelinStorageLayouts ) } @@ -317,15 +330,19 @@ export const chugsplashProposeTask = async ( 'hardhat' ) - const remoteExecution = - process.env.FORCE_REMOTE_EXECUTION === 'true' - ? true - : (await getChainId(provider)) !== hre.config.networks.hardhat.chainId + const remoteExecution = await isRemoteExecution(hre) + + const openzeppelinStorageLayouts = await importOpenZeppelinStorageLayouts( + hre, + parsedConfig, + userConfig + ) await chugsplashProposeAbstractTask( provider, signer, parsedConfig, + userConfig, configPath, ipfsUrl, silent, @@ -334,7 +351,8 @@ export const chugsplashProposeTask = async ( 'hardhat', artifactPaths, canonicalConfigPath, - skipStorageCheck + skipStorageCheck, + openzeppelinStorageLayouts ) } @@ -376,8 +394,7 @@ export const chugsplashApproveTask = async ( const canonicalConfigPath = hre.config.paths.canonicalConfigs const deploymentFolder = hre.config.paths.deployments - const remoteExecution = - (await getChainId(provider)) !== hre.config.networks.hardhat.chainId + const remoteExecution = await isRemoteExecution(hre) const userConfig = readUserChugSplashConfig(configPath) const artifactPaths = await getArtifactPaths( @@ -643,8 +660,7 @@ export const monitorTask = async ( const canonicalConfigPath = hre.config.paths.canonicalConfigs const deploymentFolder = hre.config.paths.deployments - const remoteExecution = - (await getChainId(provider)) !== hre.config.networks.hardhat.chainId + const remoteExecution = await isRemoteExecution(hre) const userConfig = readUserChugSplashConfig(configPath) const artifactPaths = await getArtifactPaths( @@ -802,17 +818,14 @@ task(TASK_TEST) runSuper ) => { const { show, noCompile, configPath, skipDeploy } = args - const chainId = await getChainId(hre.ethers.provider) const signer = hre.ethers.provider.getSigner() - const executor = - chainId === hre.config.networks.hardhat.chainId - ? await signer.getAddress() - : EXECUTOR + const remoteExecution = await isRemoteExecution(hre) + const executor = remoteExecution ? EXECUTOR : await signer.getAddress() const networkName = await resolveNetworkName( hre.ethers.provider, 'hardhat' ) - if (chainId === hre.config.networks.hardhat.chainId) { + if (!remoteExecution) { try { const snapshotIdPath = path.join( path.basename(hre.config.paths.deployments), @@ -877,14 +890,10 @@ task(TASK_RUN) const { deployAll, noCompile } = args if (deployAll) { const signer = hre.ethers.provider.getSigner() - const chainId = await getChainId(hre.ethers.provider) - - const confirm = - chainId === hre.config.networks.hardhat.chainId ? true : args.confirm - const executor = - chainId === hre.config.networks.hardhat.chainId - ? await signer.getAddress() - : EXECUTOR + + const remoteExecution = await isRemoteExecution(hre) + const confirm = remoteExecution ? args.confirm : true + const executor = remoteExecution ? EXECUTOR : await signer.getAddress() await initializeChugSplash(hre.ethers.provider, signer, [executor]) if (!noCompile) { await hre.run(TASK_COMPILE, { diff --git a/packages/plugins/src/hardhat/utils.ts b/packages/plugins/src/hardhat/utils.ts new file mode 100644 index 000000000..3177765a1 --- /dev/null +++ b/packages/plugins/src/hardhat/utils.ts @@ -0,0 +1,11 @@ +import { getChainId } from '@eth-optimism/core-utils' +import { HardhatRuntimeEnvironment } from 'hardhat/types' + +export const isRemoteExecution = async ( + hre: HardhatRuntimeEnvironment +): Promise => { + return process.env.FORCE_REMOTE_EXECUTION === 'true' + ? true + : (await getChainId(hre.ethers.provider)) !== + hre.config.networks.hardhat.chainId +} diff --git a/packages/plugins/test/Storage.spec.ts b/packages/plugins/test/Storage.spec.ts index 76c1c6011..ecf8c7b35 100644 --- a/packages/plugins/test/Storage.spec.ts +++ b/packages/plugins/test/Storage.spec.ts @@ -53,6 +53,10 @@ describe('Storage', () => { expect(await MyStorage.longBytesTest()).equals(variables.longBytesTest) }) + it('does set bytes32', async () => { + expect(await MyStorage.bytes32Test()).equals(variables.bytes32Test) + }) + it('does set contract', async () => { expect(await MyStorage.contractTest()).equals(variables.contractTest) }) diff --git a/packages/plugins/test/Transfer.spec.ts b/packages/plugins/test/Transfer.spec.ts index d3461f995..7312a88a6 100644 --- a/packages/plugins/test/Transfer.spec.ts +++ b/packages/plugins/test/Transfer.spec.ts @@ -18,7 +18,10 @@ import { BigNumber } from 'ethers' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import * as ProxyAdminArtifact from '@openzeppelin/contracts/build/contracts/ProxyAdmin.json' -import { getArtifactPaths } from '../src/hardhat/artifacts' +import { + getArtifactPaths, + importOpenZeppelinStorageLayouts, +} from '../src/hardhat/artifacts' import uupsRegisterConfig from '../chugsplash/hardhat/UUPSUpgradableRegister.config' import uupsUpgradeConfig from '../chugsplash/hardhat/UUPSUpgradableUpgrade.config' import transparentRegisterConfig from '../chugsplash/hardhat/TransparentUpgradableRegister.config' @@ -70,17 +73,15 @@ describe('Transfer', () => { path.join(hre.config.paths.artifacts, 'build-info') ) - const parsedConfig = await parseChugSplashConfig( - provider, - transparentRegisterConfig, - artifactPaths, - 'hardhat' - ) - await chugsplashRegisterAbstractTask( provider, signer, - parsedConfig, + await parseChugSplashConfig( + provider, + transparentRegisterConfig, + artifactPaths, + 'hardhat' + ), signer.address, true, 'hardhat' @@ -103,10 +104,24 @@ describe('Transfer', () => { managerProxyAddress ) + const configPath = + './chugsplash/hardhat/TransparentUpgradableUpgrade.config.ts' + const parsedConfig = await parseChugSplashConfig( + provider, + transparentUpgradeConfig, + artifactPaths, + 'hardhat' + ) + const openzeppelinStorageLayouts = await importOpenZeppelinStorageLayouts( + hre, + parsedConfig, + transparentUpgradeConfig + ) + await chugsplashDeployAbstractTask( provider, signer, - './chugsplash/hardhat/TransparentUpgradableUpgrade.config.ts', + configPath, true, false, '', @@ -119,7 +134,8 @@ describe('Transfer', () => { deploymentFolder, 'hardhat', true, - hre.chugsplash.executor + hre.chugsplash.executor, + openzeppelinStorageLayouts ) const TransparentUpgradableTokenV2 = await hre.chugsplash.getContract( @@ -176,7 +192,7 @@ describe('Transfer', () => { const parsedConfig = await parseChugSplashConfig( provider, - uupsRegisterConfig, + uupsUpgradeConfig, artifactPaths, 'hardhat' ) @@ -202,10 +218,17 @@ describe('Transfer', () => { 'proxy owner is not chugsplash manager' ) + const configPath = './chugsplash/hardhat/UUPSUpgradableUpgrade.config.ts' + const openzeppelinStorageLayouts = await importOpenZeppelinStorageLayouts( + hre, + parsedConfig, + uupsUpgradeConfig + ) + await chugsplashDeployAbstractTask( provider, signer, - './chugsplash/hardhat/UUPSUpgradableUpgrade.config.ts', + configPath, true, false, '', @@ -218,7 +241,8 @@ describe('Transfer', () => { deploymentFolder, 'hardhat', true, - hre.chugsplash.executor + hre.chugsplash.executor, + openzeppelinStorageLayouts ) const UUPSUpgradableTokenV2 = await hre.chugsplash.getContract( diff --git a/packages/plugins/test/constants.ts b/packages/plugins/test/constants.ts index 858e90221..474f2ee75 100644 --- a/packages/plugins/test/constants.ts +++ b/packages/plugins/test/constants.ts @@ -15,6 +15,7 @@ export const variables = { longStringTest: 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz', bytesTest: '0xabcd1234', + bytes32Test: '0x' + '11'.repeat(32), longBytesTest: '0x123456789101112131415161718192021222324252627282930313233343536373839404142434445464', contractTest: '0x' + '11'.repeat(20), diff --git a/yarn.lock b/yarn.lock index 91607b89b..03da75e20 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1493,7 +1493,7 @@ debug "^4.1.1" proper-lockfile "^4.1.1" -"@openzeppelin/upgrades-core@^1.20.0", "@openzeppelin/upgrades-core@^1.20.6": +"@openzeppelin/upgrades-core@^1.20.0", "@openzeppelin/upgrades-core@^1.20.6", "@openzeppelin/upgrades-core@^1.24.0": version "1.24.0" resolved "https://registry.yarnpkg.com/@openzeppelin/upgrades-core/-/upgrades-core-1.24.0.tgz#4273a6d77922d0b3d20e09267bd9d2648ccb5cc0" integrity sha512-lXf1tUrCZ3Q/YmWhw0cuSSOHMp0OAsmeOg1fhSGEM6nQQ6cIVlFvq2pCV5hZMb7xkOm5pmmzV8JW1W3kfW6Lfw==