From 36e4e44f513126bf3e1abf631f4f22c2f0108e63 Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Tue, 4 May 2021 16:01:43 -0400 Subject: [PATCH 1/5] wip: Start chugsplash hardhat tooling --- packages/contracts/src/chugsplash/actions.ts | 2 +- .../contracts/src/chugsplash/hardhat-tools.ts | 52 +++++++++++++++++++ packages/contracts/src/chugsplash/index.ts | 1 + .../test/chugsplash/hardhat-tools.spec.ts | 24 +++++++++ 4 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 packages/contracts/src/chugsplash/hardhat-tools.ts create mode 100644 packages/contracts/test/chugsplash/hardhat-tools.spec.ts diff --git a/packages/contracts/src/chugsplash/actions.ts b/packages/contracts/src/chugsplash/actions.ts index 66fdc7600fc..180e79a1e9e 100644 --- a/packages/contracts/src/chugsplash/actions.ts +++ b/packages/contracts/src/chugsplash/actions.ts @@ -1,4 +1,4 @@ -/* External Imports */ +/* Imports: External */ import { fromHexString, toHexString } from '@eth-optimism/core-utils' import { ethers } from 'ethers' import MerkleTree from 'merkletreejs' diff --git a/packages/contracts/src/chugsplash/hardhat-tools.ts b/packages/contracts/src/chugsplash/hardhat-tools.ts new file mode 100644 index 00000000000..ed694f7c3ab --- /dev/null +++ b/packages/contracts/src/chugsplash/hardhat-tools.ts @@ -0,0 +1,52 @@ +/* Imports: External */ +import { HardhatRuntimeEnvironment } from 'hardhat/types' + +/* Imports: Internal */ +import { computeStorageSlots, getStorageLayout } from './storage' +import { ChugSplashConfig, parseChugSplashConfig } from './config' +import { + ChugSplashAction, + ChugSplashActionBundle, + getChugSplashActionBundle, +} from './actions' + +/** + * Generates a ChugSplash action bundle from a config file. + * @param hre Hardhat runtime environment, used to load artifacts + storage layouts. + * @param config Config file to convert into a bundle. + * @param env Environment variables to inject into the config file. + * @returns Action bundle generated from the parsed config file. + */ +export const makeActionBundleFromConfig = async ( + hre: HardhatRuntimeEnvironment, + config: ChugSplashConfig, + env: any = {} +): Promise => { + const parsed = parseChugSplashConfig(config, env) + + const actions: ChugSplashAction[] = [] + for (const [contractName, contractConfig] of Object.entries( + parsed.contracts + )) { + const artifact = hre.artifacts.readArtifactSync(contractConfig.source) + const storageLayout = await getStorageLayout(hre, contractConfig.source) + + actions.push({ + target: contractConfig.address, + code: artifact.deployedBytecode, + }) + + for (const slot of computeStorageSlots( + storageLayout, + contractConfig.variables + )) { + actions.push({ + target: contractConfig.address, + key: slot.key, + value: slot.val, + }) + } + } + + return getChugSplashActionBundle(actions) +} diff --git a/packages/contracts/src/chugsplash/index.ts b/packages/contracts/src/chugsplash/index.ts index 8bafc869d65..1d288f94bb2 100644 --- a/packages/contracts/src/chugsplash/index.ts +++ b/packages/contracts/src/chugsplash/index.ts @@ -1,3 +1,4 @@ export * from './actions' export * from './config' export * from './storage' +export * from './hardhat-tools' diff --git a/packages/contracts/test/chugsplash/hardhat-tools.spec.ts b/packages/contracts/test/chugsplash/hardhat-tools.spec.ts new file mode 100644 index 00000000000..48f928e5ddf --- /dev/null +++ b/packages/contracts/test/chugsplash/hardhat-tools.spec.ts @@ -0,0 +1,24 @@ +import { expect } from '../setup' + +/* Imports: External */ +import hre from 'hardhat' + +/* Imports: Internal */ +import { makeActionBundleFromConfig } from '../../src' + +describe('ChugSplash hardhat tooling', () => { + describe('makeActionBundleFromConfig', () => { + it('should generate an action bundle from a basic config file', async () => { + // TODO: What's the best way to test this? + await makeActionBundleFromConfig(hre, { + contracts: { + MyContract: { + address: `0x${'11'.repeat(20)}`, + source: 'OVM_ExecutionManager', + variables: {}, + }, + }, + }) + }) + }) +}) From 12ad10be6341bf2810c5877b92e2366e800d8dfc Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Tue, 4 May 2021 16:05:45 -0400 Subject: [PATCH 2/5] docs: add some comments --- packages/contracts/src/chugsplash/hardhat-tools.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/contracts/src/chugsplash/hardhat-tools.ts b/packages/contracts/src/chugsplash/hardhat-tools.ts index ed694f7c3ab..74d8813cafa 100644 --- a/packages/contracts/src/chugsplash/hardhat-tools.ts +++ b/packages/contracts/src/chugsplash/hardhat-tools.ts @@ -22,6 +22,7 @@ export const makeActionBundleFromConfig = async ( config: ChugSplashConfig, env: any = {} ): Promise => { + // Parse the config to replace any template variables. const parsed = parseChugSplashConfig(config, env) const actions: ChugSplashAction[] = [] @@ -31,11 +32,13 @@ export const makeActionBundleFromConfig = async ( const artifact = hre.artifacts.readArtifactSync(contractConfig.source) const storageLayout = await getStorageLayout(hre, contractConfig.source) + // Add a SET_CODE action for each contract first. actions.push({ target: contractConfig.address, code: artifact.deployedBytecode, }) + // Add SET_STORAGE actions for each storage slot that we want to modify. for (const slot of computeStorageSlots( storageLayout, contractConfig.variables @@ -48,5 +51,6 @@ export const makeActionBundleFromConfig = async ( } } + // Generate a bundle from the list of actions. return getChugSplashActionBundle(actions) } From 9d066c5bf54c6f296edb3278e0158111d33d08fa Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Tue, 4 May 2021 17:08:47 -0400 Subject: [PATCH 3/5] style: break storage slot compute line into two lines --- packages/contracts/src/chugsplash/hardhat-tools.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/contracts/src/chugsplash/hardhat-tools.ts b/packages/contracts/src/chugsplash/hardhat-tools.ts index 74d8813cafa..89367da3ae2 100644 --- a/packages/contracts/src/chugsplash/hardhat-tools.ts +++ b/packages/contracts/src/chugsplash/hardhat-tools.ts @@ -39,10 +39,8 @@ export const makeActionBundleFromConfig = async ( }) // Add SET_STORAGE actions for each storage slot that we want to modify. - for (const slot of computeStorageSlots( - storageLayout, - contractConfig.variables - )) { + const slots = computeStorageSlots(storageLayout, contractConfig.variables) + for (const slot of slots) { actions.push({ target: contractConfig.address, key: slot.key, From 5a56e0cb9a5c256f045c51347425a22d935c48bf Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Thu, 6 May 2021 17:27:31 -0400 Subject: [PATCH 4/5] test: Add tests for hardhat tooling --- packages/contracts/src/chugsplash/config.ts | 1 + .../test/chugsplash/hardhat-tools.spec.ts | 312 +++++++++++++++++- 2 files changed, 308 insertions(+), 5 deletions(-) diff --git a/packages/contracts/src/chugsplash/config.ts b/packages/contracts/src/chugsplash/config.ts index 2667dc75247..0a902bc2af6 100644 --- a/packages/contracts/src/chugsplash/config.ts +++ b/packages/contracts/src/chugsplash/config.ts @@ -3,6 +3,7 @@ import * as Handlebars from 'handlebars' import { ethers } from 'ethers' type SolidityVariable = + | boolean | string | number | Array diff --git a/packages/contracts/test/chugsplash/hardhat-tools.spec.ts b/packages/contracts/test/chugsplash/hardhat-tools.spec.ts index 48f928e5ddf..4cf9404a469 100644 --- a/packages/contracts/test/chugsplash/hardhat-tools.spec.ts +++ b/packages/contracts/test/chugsplash/hardhat-tools.spec.ts @@ -2,23 +2,325 @@ import { expect } from '../setup' /* Imports: External */ import hre from 'hardhat' +import { ethers } from 'ethers' +import { remove0x } from '@eth-optimism/core-utils' /* Imports: Internal */ -import { makeActionBundleFromConfig } from '../../src' +import { + ChugSplashActionType, + getContractDefinition, + makeActionBundleFromConfig, +} from '../../src' +import { NON_NULL_BYTES32, NON_ZERO_ADDRESS } from '../helpers' describe('ChugSplash hardhat tooling', () => { describe('makeActionBundleFromConfig', () => { - it('should generate an action bundle from a basic config file', async () => { - // TODO: What's the best way to test this? - await makeActionBundleFromConfig(hre, { + it('shoulld make a bundle from config with one contract and no variables', async () => { + const bundle = await makeActionBundleFromConfig(hre, { contracts: { MyContract: { address: `0x${'11'.repeat(20)}`, - source: 'OVM_ExecutionManager', + source: 'Helper_StorageHelper', variables: {}, }, }, }) + + expect(bundle.actions.length).to.equal(1) + expect(bundle.actions[0].action).to.deep.equal({ + actionType: ChugSplashActionType.SET_CODE, + target: `0x${'11'.repeat(20)}`, + data: getContractDefinition('Helper_StorageHelper').deployedBytecode, + }) + }) + + it('should make a bundle from config with two contracts and no variables', async () => { + const bundle = await makeActionBundleFromConfig(hre, { + contracts: { + MyContract1: { + address: `0x${'11'.repeat(20)}`, + source: 'Helper_StorageHelper', + variables: {}, + }, + MyContract2: { + address: `0x${'22'.repeat(20)}`, + source: 'Helper_StorageHelper', + variables: {}, + }, + }, + }) + + expect(bundle.actions.length).to.equal(2) + expect(bundle.actions[0].action).to.deep.equal({ + actionType: ChugSplashActionType.SET_CODE, + target: `0x${'11'.repeat(20)}`, + data: getContractDefinition('Helper_StorageHelper').deployedBytecode, + }) + expect(bundle.actions[1].action).to.deep.equal({ + actionType: ChugSplashActionType.SET_CODE, + target: `0x${'22'.repeat(20)}`, + data: getContractDefinition('Helper_StorageHelper').deployedBytecode, + }) + }) + + it('should make a bundle from config with one contract with variables', async () => { + const bundle = await makeActionBundleFromConfig(hre, { + contracts: { + MyContract1: { + address: `0x${'11'.repeat(20)}`, + source: 'Helper_StorageHelper', + variables: { + _uint8: 123, + _bytes32: NON_NULL_BYTES32, + }, + }, + }, + }) + + expect(bundle.actions.length).to.equal(3) + expect(bundle.actions[0].action).to.deep.equal({ + actionType: ChugSplashActionType.SET_CODE, + target: `0x${'11'.repeat(20)}`, + data: getContractDefinition('Helper_StorageHelper').deployedBytecode, + }) + expect(bundle.actions[1].action).to.deep.equal({ + actionType: ChugSplashActionType.SET_STORAGE, + target: `0x${'11'.repeat(20)}`, + data: ethers.utils.defaultAbiCoder.encode( + ['bytes32', 'bytes32'], + [ + ethers.constants.HashZero, + '0x000000000000000000000000000000000000000000000000000000000000007b', + ] + ), + }) + expect(bundle.actions[2].action).to.deep.equal({ + actionType: ChugSplashActionType.SET_STORAGE, + target: `0x${'11'.repeat(20)}`, + data: ethers.utils.defaultAbiCoder.encode( + ['bytes32', 'bytes32'], + [ + '0x000000000000000000000000000000000000000000000000000000000000000a', + NON_NULL_BYTES32, + ] + ), + }) + }) + + it('should make a bundle from config with two contracts with variables', async () => { + const bundle = await makeActionBundleFromConfig(hre, { + contracts: { + MyContract1: { + address: `0x${'11'.repeat(20)}`, + source: 'Helper_StorageHelper', + variables: { + _uint8: 123, + _bytes32: NON_NULL_BYTES32, + }, + }, + MyContract2: { + address: `0x${'22'.repeat(20)}`, + source: 'Helper_StorageHelper', + variables: { + _address: NON_ZERO_ADDRESS, + _bool: true, + }, + }, + }, + }) + + expect(bundle.actions.length).to.equal(6) + expect(bundle.actions[0].action).to.deep.equal({ + actionType: ChugSplashActionType.SET_CODE, + target: `0x${'11'.repeat(20)}`, + data: getContractDefinition('Helper_StorageHelper').deployedBytecode, + }) + expect(bundle.actions[1].action).to.deep.equal({ + actionType: ChugSplashActionType.SET_STORAGE, + target: `0x${'11'.repeat(20)}`, + data: ethers.utils.defaultAbiCoder.encode( + ['bytes32', 'bytes32'], + [ + ethers.constants.HashZero, + '0x000000000000000000000000000000000000000000000000000000000000007b', + ] + ), + }) + expect(bundle.actions[2].action).to.deep.equal({ + actionType: ChugSplashActionType.SET_STORAGE, + target: `0x${'11'.repeat(20)}`, + data: ethers.utils.defaultAbiCoder.encode( + ['bytes32', 'bytes32'], + [ + '0x000000000000000000000000000000000000000000000000000000000000000a', + NON_NULL_BYTES32, + ] + ), + }) + expect(bundle.actions[3].action).to.deep.equal({ + actionType: ChugSplashActionType.SET_CODE, + target: `0x${'22'.repeat(20)}`, + data: getContractDefinition('Helper_StorageHelper').deployedBytecode, + }) + expect(bundle.actions[4].action).to.deep.equal({ + actionType: ChugSplashActionType.SET_STORAGE, + target: `0x${'22'.repeat(20)}`, + data: ethers.utils.defaultAbiCoder.encode( + ['bytes32', 'bytes32'], + [ + '0x000000000000000000000000000000000000000000000000000000000000000e', + `0x000000000000000000000000${remove0x(NON_ZERO_ADDRESS)}`, + ] + ), + }) + expect(bundle.actions[5].action).to.deep.equal({ + actionType: ChugSplashActionType.SET_STORAGE, + target: `0x${'22'.repeat(20)}`, + data: ethers.utils.defaultAbiCoder.encode( + ['bytes32', 'bytes32'], + [ + '0x000000000000000000000000000000000000000000000000000000000000000c', + '0x0000000000000000000000000000000000000000000000000000000000000001', + ] + ), + }) + }) + + it('should make a bundle from config with one contract and templated variables', async () => { + const bundle = await makeActionBundleFromConfig( + hre, + { + contracts: { + MyContract1: { + address: `0x${'11'.repeat(20)}`, + source: 'Helper_StorageHelper', + variables: { + _uint8: `{{ env.MY_UINT8_VALUE }}`, + _bytes32: `{{ env.MY_BYTES32_VALUE }}`, + }, + }, + }, + }, + { + MY_UINT8_VALUE: 123, + MY_BYTES32_VALUE: NON_NULL_BYTES32, + } + ) + + expect(bundle.actions.length).to.equal(3) + expect(bundle.actions[0].action).to.deep.equal({ + actionType: ChugSplashActionType.SET_CODE, + target: `0x${'11'.repeat(20)}`, + data: getContractDefinition('Helper_StorageHelper').deployedBytecode, + }) + expect(bundle.actions[1].action).to.deep.equal({ + actionType: ChugSplashActionType.SET_STORAGE, + target: `0x${'11'.repeat(20)}`, + data: ethers.utils.defaultAbiCoder.encode( + ['bytes32', 'bytes32'], + [ + ethers.constants.HashZero, + '0x000000000000000000000000000000000000000000000000000000000000007b', + ] + ), + }) + expect(bundle.actions[2].action).to.deep.equal({ + actionType: ChugSplashActionType.SET_STORAGE, + target: `0x${'11'.repeat(20)}`, + data: ethers.utils.defaultAbiCoder.encode( + ['bytes32', 'bytes32'], + [ + '0x000000000000000000000000000000000000000000000000000000000000000a', + NON_NULL_BYTES32, + ] + ), + }) + }) + + it('should make a bundle from config with two contracts with variables and templated variables', async () => { + const bundle = await makeActionBundleFromConfig( + hre, + { + contracts: { + MyContract1: { + address: `0x${'11'.repeat(20)}`, + source: 'Helper_StorageHelper', + variables: { + _uint8: 123, + _bytes32: NON_NULL_BYTES32, + }, + }, + MyContract2: { + address: `0x${'22'.repeat(20)}`, + source: 'Helper_StorageHelper', + variables: { + _address: `{{ env.MY_ADDRESS_VALUE }}`, + _bool: `{{ env.MY_BOOLEAN_VALUE }}`, + }, + }, + }, + }, + { + MY_ADDRESS_VALUE: NON_ZERO_ADDRESS, + MY_BOOLEAN_VALUE: true, + } + ) + + expect(bundle.actions.length).to.equal(6) + expect(bundle.actions[0].action).to.deep.equal({ + actionType: ChugSplashActionType.SET_CODE, + target: `0x${'11'.repeat(20)}`, + data: getContractDefinition('Helper_StorageHelper').deployedBytecode, + }) + expect(bundle.actions[1].action).to.deep.equal({ + actionType: ChugSplashActionType.SET_STORAGE, + target: `0x${'11'.repeat(20)}`, + data: ethers.utils.defaultAbiCoder.encode( + ['bytes32', 'bytes32'], + [ + ethers.constants.HashZero, + '0x000000000000000000000000000000000000000000000000000000000000007b', + ] + ), + }) + expect(bundle.actions[2].action).to.deep.equal({ + actionType: ChugSplashActionType.SET_STORAGE, + target: `0x${'11'.repeat(20)}`, + data: ethers.utils.defaultAbiCoder.encode( + ['bytes32', 'bytes32'], + [ + '0x000000000000000000000000000000000000000000000000000000000000000a', + NON_NULL_BYTES32, + ] + ), + }) + expect(bundle.actions[3].action).to.deep.equal({ + actionType: ChugSplashActionType.SET_CODE, + target: `0x${'22'.repeat(20)}`, + data: getContractDefinition('Helper_StorageHelper').deployedBytecode, + }) + expect(bundle.actions[4].action).to.deep.equal({ + actionType: ChugSplashActionType.SET_STORAGE, + target: `0x${'22'.repeat(20)}`, + data: ethers.utils.defaultAbiCoder.encode( + ['bytes32', 'bytes32'], + [ + '0x000000000000000000000000000000000000000000000000000000000000000e', + `0x000000000000000000000000${remove0x(NON_ZERO_ADDRESS)}`, + ] + ), + }) + expect(bundle.actions[5].action).to.deep.equal({ + actionType: ChugSplashActionType.SET_STORAGE, + target: `0x${'22'.repeat(20)}`, + data: ethers.utils.defaultAbiCoder.encode( + ['bytes32', 'bytes32'], + [ + '0x000000000000000000000000000000000000000000000000000000000000000c', + '0x0000000000000000000000000000000000000000000000000000000000000001', + ] + ), + }) }) }) }) From 3126fa847a352e6866b89c702cca7e985bc9c342 Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Fri, 7 May 2021 17:38:59 -0400 Subject: [PATCH 5/5] fix: use stricter env type --- packages/contracts/src/chugsplash/hardhat-tools.ts | 4 +++- packages/contracts/test/chugsplash/hardhat-tools.spec.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/contracts/src/chugsplash/hardhat-tools.ts b/packages/contracts/src/chugsplash/hardhat-tools.ts index 89367da3ae2..719dc8ded11 100644 --- a/packages/contracts/src/chugsplash/hardhat-tools.ts +++ b/packages/contracts/src/chugsplash/hardhat-tools.ts @@ -20,7 +20,9 @@ import { export const makeActionBundleFromConfig = async ( hre: HardhatRuntimeEnvironment, config: ChugSplashConfig, - env: any = {} + env: { + [key: string]: string | number | boolean + } = {} ): Promise => { // Parse the config to replace any template variables. const parsed = parseChugSplashConfig(config, env) diff --git a/packages/contracts/test/chugsplash/hardhat-tools.spec.ts b/packages/contracts/test/chugsplash/hardhat-tools.spec.ts index 4cf9404a469..3843bc4541c 100644 --- a/packages/contracts/test/chugsplash/hardhat-tools.spec.ts +++ b/packages/contracts/test/chugsplash/hardhat-tools.spec.ts @@ -15,7 +15,7 @@ import { NON_NULL_BYTES32, NON_ZERO_ADDRESS } from '../helpers' describe('ChugSplash hardhat tooling', () => { describe('makeActionBundleFromConfig', () => { - it('shoulld make a bundle from config with one contract and no variables', async () => { + it('should make a bundle from config with one contract and no variables', async () => { const bundle = await makeActionBundleFromConfig(hre, { contracts: { MyContract: {