diff --git a/.changeset/cyan-months-kneel.md b/.changeset/cyan-months-kneel.md new file mode 100644 index 0000000000000..23f1a157e8693 --- /dev/null +++ b/.changeset/cyan-months-kneel.md @@ -0,0 +1,5 @@ +--- +'@eth-optimism/contracts': patch +--- + +Use hardhat-deploy-config for deployments diff --git a/.changeset/giant-cobras-heal.md b/.changeset/giant-cobras-heal.md new file mode 100644 index 0000000000000..cef9cb08d0e40 --- /dev/null +++ b/.changeset/giant-cobras-heal.md @@ -0,0 +1,5 @@ +--- +'@eth-optimism/hardhat-deploy-config': minor +--- + +Initial release of hardhat-deploy-config diff --git a/ops/docker/Dockerfile.packages b/ops/docker/Dockerfile.packages index e3d9237102633..9ae9706753e81 100644 --- a/ops/docker/Dockerfile.packages +++ b/ops/docker/Dockerfile.packages @@ -24,6 +24,7 @@ COPY packages/contracts-bedrock/package.json ./packages/contracts-bedrock/packag COPY packages/contracts-periphery/package.json ./packages/contracts-periphery/package.json COPY packages/contracts-governance/package.json ./packages/contracts-governance/package.json COPY packages/data-transport-layer/package.json ./packages/data-transport-layer/package.json +COPY packages/hardhat-deploy-config/package.json ./packages/hardhat-deploy-config/package.json COPY packages/message-relayer/package.json ./packages/message-relayer/package.json COPY packages/fault-detector/package.json ./packages/fault-detector/package.json COPY packages/replica-healthcheck/package.json ./packages/replica-healthcheck/package.json diff --git a/packages/contracts-bedrock/README.md b/packages/contracts-bedrock/README.md index bf4da246011dd..ce656709e98ac 100644 --- a/packages/contracts-bedrock/README.md +++ b/packages/contracts-bedrock/README.md @@ -43,3 +43,16 @@ To run only solidity tests: ```shell yarn test:forge ``` + +## Deployment + +Create a file that corresponds to the network name in the `deploy-config` +directory and then run the command: + +```shell +npx hardhat deploy --network +``` + +In the `hardhat.config.ts`, there is a `deployConfigSpec` field that validates that the types +are correct, be sure to export an object in the `deploy-config/.ts` file that +has a key for each property in the `deployConfigSpec`. diff --git a/packages/contracts-bedrock/deploy-config/devnetL1.ts b/packages/contracts-bedrock/deploy-config/devnetL1.ts new file mode 100644 index 0000000000000..dfecddc6b0c84 --- /dev/null +++ b/packages/contracts-bedrock/deploy-config/devnetL1.ts @@ -0,0 +1,13 @@ +import { ethers } from 'ethers' + +const config = { + submissionInterval: 6, + l2BlockTime: 2, + genesisOutput: ethers.constants.HashZero, + historicalBlocks: 0, + startingBlockTimestamp: + parseInt(process.env.L2OO_STARTING_BLOCK_TIMESTAMP, 10) || Date.now(), + sequencerAddress: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', +} + +export default config diff --git a/packages/contracts-bedrock/deploy-config/hardhat.ts b/packages/contracts-bedrock/deploy-config/hardhat.ts new file mode 100644 index 0000000000000..dfecddc6b0c84 --- /dev/null +++ b/packages/contracts-bedrock/deploy-config/hardhat.ts @@ -0,0 +1,13 @@ +import { ethers } from 'ethers' + +const config = { + submissionInterval: 6, + l2BlockTime: 2, + genesisOutput: ethers.constants.HashZero, + historicalBlocks: 0, + startingBlockTimestamp: + parseInt(process.env.L2OO_STARTING_BLOCK_TIMESTAMP, 10) || Date.now(), + sequencerAddress: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', +} + +export default config diff --git a/packages/contracts-bedrock/deploy/000-L2OutputOracle.deploy.ts b/packages/contracts-bedrock/deploy/000-L2OutputOracle.deploy.ts index 2f88bed91c28c..344b7510e2065 100644 --- a/packages/contracts-bedrock/deploy/000-L2OutputOracle.deploy.ts +++ b/packages/contracts-bedrock/deploy/000-L2OutputOracle.deploy.ts @@ -7,23 +7,23 @@ const deployFn: DeployFunction = async (hre) => { const { deployer } = await hre.getNamedAccounts() if ( - !process.env.L2OO_STARTING_BLOCK_TIMESTAMP || - isNaN(Number(process.env.L2OO_STARTING_BLOCK_TIMESTAMP)) + typeof hre.deployConfig.startingBlockTimestamp !== 'number' || + isNaN(hre.deployConfig.startingBlockTimestamp) ) { throw new Error( - 'Cannot deploy L2OutputOracle without specifying a valid L2OO_STARTING_BLOCK_TIMESTAMP.' + 'Cannot deploy L2OutputOracle without specifying a valid startingBlockTimestamp.' ) } await deploy('L2OutputOracle', { from: deployer, args: [ - 6, // submission interval - 2, // l2 block time - `0x${'00'.repeat(32)}`, // genesis output - 0, // historical blocks - process.env.L2OO_STARTING_BLOCK_TIMESTAMP, - '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', // sequencer + hre.deployConfig.submissionInterval, + hre.deployConfig.l2BlockTime, + hre.deployConfig.genesisOutput, + hre.deployConfig.historicalBlocks, + hre.deployConfig.startingBlockTimestamp, + hre.deployConfig.sequencerAddress, ], log: true, waitConfirmations: 1, diff --git a/packages/contracts-bedrock/hardhat.config.ts b/packages/contracts-bedrock/hardhat.config.ts index cbab5a1c18251..9b1164e36254b 100644 --- a/packages/contracts-bedrock/hardhat.config.ts +++ b/packages/contracts-bedrock/hardhat.config.ts @@ -1,3 +1,4 @@ +import { ethers } from 'ethers' import { HardhatUserConfig, task, subtask } from 'hardhat/config' import { TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS } from 'hardhat/builtin-tasks/task-names' import '@nomiclabs/hardhat-waffle' @@ -5,6 +6,7 @@ import '@typechain/hardhat' import 'solidity-coverage' import 'hardhat-deploy' import '@foundry-rs/hardhat-forge' +import '@eth-optimism/hardhat-deploy-config' import './tasks/deposits' @@ -33,6 +35,11 @@ const config: HardhatUserConfig = { ], }, }, + paths: { + deploy: './deploy', + deployments: './deployments', + deployConfig: './deploy-config', + }, typechain: { outDir: 'dist/types', target: 'ethers-v5', @@ -42,6 +49,27 @@ const config: HardhatUserConfig = { default: 0, }, }, + deployConfigSpec: { + submissionInterval: { + type: 'number', + }, + l2BlockTime: { + type: 'number', + }, + genesisOutput: { + type: 'string', + default: ethers.constants.HashZero, + }, + historicalBlocks: { + type: 'number', + }, + startingBlockTimestamp: { + type: 'number', + }, + sequencerAddress: { + type: 'address', + }, + }, solidity: { compilers: [ { diff --git a/packages/contracts-bedrock/package.json b/packages/contracts-bedrock/package.json index e8d95e807a0de..cafd51cb20527 100644 --- a/packages/contracts-bedrock/package.json +++ b/packages/contracts-bedrock/package.json @@ -42,6 +42,7 @@ }, "devDependencies": { "@foundry-rs/hardhat-forge": "^0.1.5", + "@eth-optimism/hardhat-deploy-config": "^0.1.0", "@nomiclabs/hardhat-ethers": "^2.0.0", "@nomiclabs/hardhat-etherscan": "^2.1.3", "@nomiclabs/hardhat-waffle": "^2.0.0", diff --git a/packages/contracts/deploy-config/goerli-nightly.ts b/packages/contracts/deploy-config/goerli-nightly.ts index dbaab4dff4d45..61ad2995644de 100644 --- a/packages/contracts/deploy-config/goerli-nightly.ts +++ b/packages/contracts/deploy-config/goerli-nightly.ts @@ -1,6 +1,4 @@ -import { DeployConfig } from '../src/deploy-config' - -const config: DeployConfig = { +const config = { l1BlockTimeSeconds: 15, l2BlockGasLimit: 15_000_000, l2ChainId: 421, diff --git a/packages/contracts/deploy-config/goerli.ts b/packages/contracts/deploy-config/goerli.ts index b10b4c72d0ca2..46334fe953e5a 100644 --- a/packages/contracts/deploy-config/goerli.ts +++ b/packages/contracts/deploy-config/goerli.ts @@ -1,6 +1,4 @@ -import { DeployConfig } from '../src/deploy-config' - -const config: DeployConfig = { +const config = { numDeployConfirmations: 1, l1BlockTimeSeconds: 15, l2BlockGasLimit: 15_000_000, diff --git a/packages/contracts/deploy-config/hardhat.ts b/packages/contracts/deploy-config/hardhat.ts new file mode 100644 index 0000000000000..b20aec0cb9513 --- /dev/null +++ b/packages/contracts/deploy-config/hardhat.ts @@ -0,0 +1,17 @@ +const config = { + l1BlockTimeSeconds: 15, + l2BlockGasLimit: 15_000_000, + l2ChainId: 17, + ctcL2GasDiscountDivisor: 32, + ctcEnqueueGasCost: 60_000, + sccFaultProofWindowSeconds: 0, + sccSequencerPublishWindowSeconds: 12592000, + ovmSequencerAddress: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + ovmProposerAddress: '0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc', + ovmBlockSignerAddress: '0x00000398232E2064F896018496b4b44b3D62751F', + ovmFeeWalletAddress: '0x391716d440c151c42cdf1c95c1d83a5427bca52c', + ovmAddressManagerOwner: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + ovmGasPriceOracleOwner: '0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc', +} + +export default config diff --git a/packages/contracts/deploy-config/kovan.ts b/packages/contracts/deploy-config/kovan.ts index 5879ab7af6283..595b86277e981 100644 --- a/packages/contracts/deploy-config/kovan.ts +++ b/packages/contracts/deploy-config/kovan.ts @@ -1,6 +1,4 @@ -import { DeployConfig } from '../src/deploy-config' - -const config: DeployConfig = { +const config = { numDeployConfirmations: 1, gasPrice: 5_000_000_000, l1BlockTimeSeconds: 15, diff --git a/packages/contracts/deploy-config/local.ts b/packages/contracts/deploy-config/local.ts index 5dfc40a09009b..b20aec0cb9513 100644 --- a/packages/contracts/deploy-config/local.ts +++ b/packages/contracts/deploy-config/local.ts @@ -1,6 +1,4 @@ -import { DeployConfig } from '../src/deploy-config' - -const config: DeployConfig = { +const config = { l1BlockTimeSeconds: 15, l2BlockGasLimit: 15_000_000, l2ChainId: 17, diff --git a/packages/contracts/deploy-config/mainnet.ts b/packages/contracts/deploy-config/mainnet.ts index e30d23d8a49d7..187d4c5eb4271 100644 --- a/packages/contracts/deploy-config/mainnet.ts +++ b/packages/contracts/deploy-config/mainnet.ts @@ -1,6 +1,4 @@ -import { DeployConfig } from '../src/deploy-config' - -const config: DeployConfig = { +const config = { numDeployConfirmations: 4, gasPrice: 150_000_000_000, l1BlockTimeSeconds: 15, diff --git a/packages/contracts/deploy/000-hardhat-setup.ts b/packages/contracts/deploy/000-hardhat-setup.ts index f92e54b2224cc..24dd35dc0248e 100644 --- a/packages/contracts/deploy/000-hardhat-setup.ts +++ b/packages/contracts/deploy/000-hardhat-setup.ts @@ -9,12 +9,10 @@ import { sendImpersonatedTx, BIG_BALANCE, } from '../src/deploy-utils' -import { getDeployConfig } from '../src/deploy-config' import { names } from '../src/address-names' const deployFn: DeployFunction = async (hre) => { - const deployConfig = getDeployConfig(hre.network.name) - if (!deployConfig.isForkedNetwork) { + if (!hre.deployConfig.isForkedNetwork) { return } diff --git a/packages/contracts/deploy/001-Lib_AddressManager.deploy.ts b/packages/contracts/deploy/001-Lib_AddressManager.deploy.ts index 29bb06aeb5b92..5f1bea7bb89eb 100644 --- a/packages/contracts/deploy/001-Lib_AddressManager.deploy.ts +++ b/packages/contracts/deploy/001-Lib_AddressManager.deploy.ts @@ -2,20 +2,18 @@ import { DeployFunction } from 'hardhat-deploy/dist/types' import { names } from '../src/address-names' -import { getDeployConfig } from '../src/deploy-config' /* Imports: External */ const deployFn: DeployFunction = async (hre) => { const { deploy } = hre.deployments const { deployer } = await hre.getNamedAccounts() - const deployConfig = getDeployConfig(hre.network.name) await deploy(names.unmanaged.Lib_AddressManager, { from: deployer, args: [], log: true, - waitConfirmations: deployConfig.numDeployConfirmations, + waitConfirmations: hre.deployConfig.numDeployConfirmations, }) } diff --git a/packages/contracts/deploy/004-OVM_CanonicalTransactionChain.deploy.ts b/packages/contracts/deploy/004-OVM_CanonicalTransactionChain.deploy.ts index 99d3e99269bba..72fd41b49e578 100644 --- a/packages/contracts/deploy/004-OVM_CanonicalTransactionChain.deploy.ts +++ b/packages/contracts/deploy/004-OVM_CanonicalTransactionChain.deploy.ts @@ -6,12 +6,9 @@ import { deployAndVerifyAndThen, getContractFromArtifact, } from '../src/deploy-utils' -import { getDeployConfig } from '../src/deploy-config' import { names } from '../src/address-names' const deployFn: DeployFunction = async (hre) => { - const deployConfig = getDeployConfig(hre.network.name) - const Lib_AddressManager = await getContractFromArtifact( hre, names.unmanaged.Lib_AddressManager @@ -22,9 +19,9 @@ const deployFn: DeployFunction = async (hre) => { name: names.managed.contracts.CanonicalTransactionChain, args: [ Lib_AddressManager.address, - deployConfig.l2BlockGasLimit, - deployConfig.ctcL2GasDiscountDivisor, - deployConfig.ctcEnqueueGasCost, + hre.deployConfig.l2BlockGasLimit, + hre.deployConfig.ctcL2GasDiscountDivisor, + hre.deployConfig.ctcEnqueueGasCost, ], }) } diff --git a/packages/contracts/deploy/005-OVM_StateCommitmentChain.deploy.ts b/packages/contracts/deploy/005-OVM_StateCommitmentChain.deploy.ts index 97c9d0ecf447d..71fde0e015112 100644 --- a/packages/contracts/deploy/005-OVM_StateCommitmentChain.deploy.ts +++ b/packages/contracts/deploy/005-OVM_StateCommitmentChain.deploy.ts @@ -6,12 +6,9 @@ import { deployAndVerifyAndThen, getContractFromArtifact, } from '../src/deploy-utils' -import { getDeployConfig } from '../src/deploy-config' import { names } from '../src/address-names' const deployFn: DeployFunction = async (hre) => { - const deployConfig = getDeployConfig(hre.network.name) - const Lib_AddressManager = await getContractFromArtifact( hre, names.unmanaged.Lib_AddressManager @@ -22,8 +19,8 @@ const deployFn: DeployFunction = async (hre) => { name: names.managed.contracts.StateCommitmentChain, args: [ Lib_AddressManager.address, - deployConfig.sccFaultProofWindowSeconds, - deployConfig.sccSequencerPublishWindowSeconds, + hre.deployConfig.sccFaultProofWindowSeconds, + hre.deployConfig.sccSequencerPublishWindowSeconds, ], }) } diff --git a/packages/contracts/deploy/007-OVM_L1CrossDomainMessenger.deploy.ts b/packages/contracts/deploy/007-OVM_L1CrossDomainMessenger.deploy.ts index fa7a7b1434ba6..74279762c7c86 100644 --- a/packages/contracts/deploy/007-OVM_L1CrossDomainMessenger.deploy.ts +++ b/packages/contracts/deploy/007-OVM_L1CrossDomainMessenger.deploy.ts @@ -7,12 +7,9 @@ import { deployAndVerifyAndThen, getContractFromArtifact, } from '../src/deploy-utils' -import { getDeployConfig } from '../src/deploy-config' import { names } from '../src/address-names' const deployFn: DeployFunction = async (hre) => { - const deployConfig = getDeployConfig(hre.network.name) - const Lib_AddressManager = await getContractFromArtifact( hre, names.unmanaged.Lib_AddressManager @@ -48,7 +45,7 @@ const deployFn: DeployFunction = async (hre) => { console.log( `Transferring ownership of L1CrossDomainMessenger (implementation)...` ) - const owner = deployConfig.ovmAddressManagerOwner + const owner = hre.deployConfig.ovmAddressManagerOwner await contract.transferOwnership(owner) console.log(`Checking that contract owner was correctly set...`) diff --git a/packages/contracts/deploy/010-AddressDictator.deploy.ts b/packages/contracts/deploy/010-AddressDictator.deploy.ts index ab661d7a8b4ba..b8ea8f91b0700 100644 --- a/packages/contracts/deploy/010-AddressDictator.deploy.ts +++ b/packages/contracts/deploy/010-AddressDictator.deploy.ts @@ -7,13 +7,10 @@ import { deployAndVerifyAndThen, getContractFromArtifact, } from '../src/deploy-utils' -import { getDeployConfig } from '../src/deploy-config' import { names } from '../src/address-names' import { predeploys } from '../src/predeploys' const deployFn: DeployFunction = async (hre) => { - const deployConfig = getDeployConfig(hre.network.name) - const Lib_AddressManager = await getContractFromArtifact( hre, names.unmanaged.Lib_AddressManager @@ -45,13 +42,13 @@ const deployFn: DeployFunction = async (hre) => { // CanonicalTransactionChain. { name: names.managed.accounts.OVM_Sequencer, - address: deployConfig.ovmSequencerAddress, + address: hre.deployConfig.ovmSequencerAddress, }, // OVM_Proposer is the address allowed to submit state roots (transaction results) to the // StateCommitmentChain. { name: names.managed.accounts.OVM_Proposer, - address: deployConfig.ovmProposerAddress, + address: hre.deployConfig.ovmProposerAddress, }, ] @@ -72,7 +69,7 @@ const deployFn: DeployFunction = async (hre) => { name: names.unmanaged.AddressDictator, args: [ Lib_AddressManager.address, - deployConfig.ovmAddressManagerOwner, + hre.deployConfig.ovmAddressManagerOwner, namesAndAddresses.map((pair) => { return pair.name }), diff --git a/packages/contracts/deploy/012-initialize-Proxy__L1CrossDomainMessenger.ts b/packages/contracts/deploy/012-initialize-Proxy__L1CrossDomainMessenger.ts index a25730e86ff07..66e1d07e71ce3 100644 --- a/packages/contracts/deploy/012-initialize-Proxy__L1CrossDomainMessenger.ts +++ b/packages/contracts/deploy/012-initialize-Proxy__L1CrossDomainMessenger.ts @@ -4,11 +4,9 @@ import { hexStringEquals, awaitCondition } from '@eth-optimism/core-utils' /* Imports: Internal */ import { getContractFromArtifact } from '../src/deploy-utils' -import { getDeployConfig } from '../src/deploy-config' import { names } from '../src/address-names' const deployFn: DeployFunction = async (hre) => { - const deployConfig = getDeployConfig(hre.network.name) const { deployer } = await hre.getNamedAccounts() // There's a risk that we could get front-run during a fresh deployment, which would brick this @@ -45,7 +43,7 @@ const deployFn: DeployFunction = async (hre) => { ) console.log(`Setting Proxy__OVM_L1CrossDomainMessenger owner...`) - const owner = deployConfig.ovmAddressManagerOwner + const owner = hre.deployConfig.ovmAddressManagerOwner await Proxy__OVM_L1CrossDomainMessenger.transferOwnership(owner) console.log(`Checking that the contract owner was correctly set...`) diff --git a/packages/contracts/deploy/013-ChugSplashDictator.deploy.ts b/packages/contracts/deploy/013-ChugSplashDictator.deploy.ts index 60a2c20039543..088a7e985cafe 100644 --- a/packages/contracts/deploy/013-ChugSplashDictator.deploy.ts +++ b/packages/contracts/deploy/013-ChugSplashDictator.deploy.ts @@ -9,12 +9,9 @@ import { getContractFromArtifact, deployAndVerifyAndThen, } from '../src/deploy-utils' -import { getDeployConfig } from '../src/deploy-config' import { names } from '../src/address-names' const deployFn: DeployFunction = async (hre) => { - const deployConfig = getDeployConfig(hre.network.name) - const Proxy__OVM_L1StandardBridge = await getContractFromArtifact( hre, 'Proxy__OVM_L1StandardBridge' @@ -34,7 +31,7 @@ const deployFn: DeployFunction = async (hre) => { name: names.unmanaged.ChugSplashDictator, args: [ Proxy__OVM_L1StandardBridge.address, - deployConfig.ovmAddressManagerOwner, + hre.deployConfig.ovmAddressManagerOwner, ethers.utils.keccak256(bridgeCode), ethers.utils.hexZeroPad('0x00', 32), ethers.utils.hexZeroPad(Proxy__OVM_L1CrossDomainMessenger.address, 32), diff --git a/packages/contracts/deploy/015-finalize.ts b/packages/contracts/deploy/015-finalize.ts index 08a518db24ce7..19de31c3525df 100644 --- a/packages/contracts/deploy/015-finalize.ts +++ b/packages/contracts/deploy/015-finalize.ts @@ -4,10 +4,8 @@ import { hexStringEquals, awaitCondition } from '@eth-optimism/core-utils' /* Imports: Internal */ import { getContractFromArtifact } from '../src/deploy-utils' -import { getDeployConfig } from '../src/deploy-config' const deployFn: DeployFunction = async (hre) => { - const deployConfig = getDeployConfig(hre.network.name) const { deployer } = await hre.getNamedAccounts() const Lib_AddressManager = await getContractFromArtifact( @@ -18,7 +16,7 @@ const deployFn: DeployFunction = async (hre) => { } ) - const owner = deployConfig.ovmAddressManagerOwner + const owner = hre.deployConfig.ovmAddressManagerOwner const remoteOwner = await Lib_AddressManager.owner() if (hexStringEquals(owner, remoteOwner)) { console.log( diff --git a/packages/contracts/hardhat.config.ts b/packages/contracts/hardhat.config.ts index 39f9678246957..fc061bb6733fd 100644 --- a/packages/contracts/hardhat.config.ts +++ b/packages/contracts/hardhat.config.ts @@ -1,6 +1,7 @@ import { HardhatUserConfig } from 'hardhat/types' import 'solidity-coverage' import * as dotenv from 'dotenv' +import { ethers } from 'ethers' // Hardhat plugins import '@nomiclabs/hardhat-ethers' @@ -11,6 +12,7 @@ import '@typechain/hardhat' import 'hardhat-deploy' import 'hardhat-gas-reporter' import 'hardhat-output-validator' +import '@eth-optimism/hardhat-deploy-config' // Hardhat tasks import './tasks' @@ -22,7 +24,7 @@ const enableGasReport = !!process.env.ENABLE_GAS_REPORT const privateKey = process.env.PRIVATE_KEY || '0x' + '11'.repeat(32) // this is to avoid hardhat error const deploy = process.env.DEPLOY_DIRECTORY || 'deploy' -const config: HardhatUserConfig | any = { +const config: HardhatUserConfig = { networks: { hardhat: { live: false, @@ -99,6 +101,7 @@ const config: HardhatUserConfig | any = { paths: { deploy: './deploy', deployments: './deployments', + deployConfig: './deploy-config', }, namedAccounts: { deployer: { @@ -146,6 +149,87 @@ const config: HardhatUserConfig | any = { }, exclude: ['contracts/test-helpers', 'contracts/test-libraries'], }, + deployConfigSpec: { + isForkedNetwork: { + type: 'boolean', + default: false, + }, + numDeployConfirmations: { + type: 'number', + default: 0, + }, + gasPrice: { + type: 'number', + default: undefined, + }, + l1BlockTimeSeconds: { + type: 'number', + }, + l2BlockGasLimit: { + type: 'number', + }, + l2ChainId: { + type: 'number', + }, + ctcL2GasDiscountDivisor: { + type: 'number', + }, + ctcEnqueueGasCost: { + type: 'number', + }, + sccFaultProofWindowSeconds: { + type: 'number', + }, + sccSequencerPublishWindowSeconds: { + type: 'number', + }, + ovmSequencerAddress: { + type: 'address', + }, + ovmProposerAddress: { + type: 'address', + }, + ovmBlockSignerAddress: { + type: 'address', + }, + ovmFeeWalletAddress: { + type: 'address', + }, + ovmAddressManagerOwner: { + type: 'address', + }, + ovmGasPriceOracleOwner: { + type: 'address', + }, + ovmWhitelistOwner: { + type: 'address', + default: ethers.constants.AddressZero, + }, + gasPriceOracleOverhead: { + type: 'number', + default: 2750, + }, + gasPriceOracleScalar: { + type: 'number', + default: 1_500_000, + }, + gasPriceOracleDecimals: { + type: 'number', + default: 6, + }, + gasPriceOracleL1BaseFee: { + type: 'number', + default: 1, + }, + gasPriceOracleL2GasPrice: { + type: 'number', + default: 1, + }, + hfBerlinBlock: { + type: 'number', + default: 0, + }, + }, } if ( diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 0690346f5c1ef..86ed6563f40bb 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -64,6 +64,7 @@ "devDependencies": { "@codechecks/client": "^0.1.11", "@defi-wonderland/smock": "^2.0.2", + "@eth-optimism/hardhat-deploy-config": "^0.1.0", "@ethersproject/abi": "^5.6.3", "@ethersproject/bytes": "^5.6.1", "@ethersproject/hardware-wallets": "^5.6.1", diff --git a/packages/contracts/src/deploy-utils.ts b/packages/contracts/src/deploy-utils.ts index 971ab024d1023..3622fa4f43fe5 100644 --- a/packages/contracts/src/deploy-utils.ts +++ b/packages/contracts/src/deploy-utils.ts @@ -4,8 +4,6 @@ import { Signer } from '@ethersproject/abstract-signer' import { sleep, awaitCondition, getChainId } from '@eth-optimism/core-utils' import { HttpNetworkConfig } from 'hardhat/types' -import { getDeployConfig } from './deploy-config' - /** * @param {Any} hre Hardhat runtime environment * @param {String} name Contract name from the names object @@ -32,14 +30,13 @@ export const deployAndVerifyAndThen = async ({ }) => { const { deploy } = hre.deployments const { deployer } = await hre.getNamedAccounts() - const deployConfig = getDeployConfig(hre.network.name) const result = await deploy(name, { contract, from: deployer, args, log: true, - waitConfirmations: deployConfig.numDeployConfirmations, + waitConfirmations: hre.deployConfig.numDeployConfirmations, }) await hre.ethers.provider.waitForTransaction(result.transactionHash) @@ -94,8 +91,6 @@ export const getAdvancedContract = (opts: { hre: any contract: Contract }): Contract => { - const deployConfig = getDeployConfig(opts.hre.network.name) - // Temporarily override Object.defineProperty to bypass ether's object protection. const def = Object.defineProperty Object.defineProperty = (obj, propName, prop) => { @@ -119,7 +114,7 @@ export const getAdvancedContract = (opts: { // We want to use the gas price that has been configured at the beginning of the deployment. // However, if the function being triggered is a "constant" (static) function, then we don't // want to provide a gas price because we're prone to getting insufficient balance errors. - let gasPrice = deployConfig.gasPrice || undefined + let gasPrice = opts.hre.deployConfig.gasPrice || undefined if (contract.interface.getFunction(fnName).constant) { gasPrice = 0 } @@ -151,7 +146,7 @@ export const getAdvancedContract = (opts: { return contract[fnName](...args) } } else if ( - receipt.confirmations >= deployConfig.numDeployConfirmations + receipt.confirmations >= opts.hre.deployConfig.numDeployConfirmations ) { return tx } @@ -167,9 +162,7 @@ export const fundAccount = async ( address: string, amount: ethers.BigNumber ) => { - const deployConfig = getDeployConfig(hre.network.name) - - if (!deployConfig.isForkedNetwork) { + if (!hre.deployConfig.isForkedNetwork) { throw new Error('this method can only be used against a forked network') } @@ -200,9 +193,7 @@ export const sendImpersonatedTx = async (opts: { gas: string args: any[] }) => { - const deployConfig = getDeployConfig(opts.hre.network.name) - - if (!deployConfig.isForkedNetwork) { + if (!opts.hre.deployConfig.isForkedNetwork) { throw new Error('this method can only be used against a forked network') } diff --git a/packages/contracts/tasks/take-dump.ts b/packages/contracts/tasks/take-dump.ts index 0d5232eed4b6c..1ccda7b1c8b70 100644 --- a/packages/contracts/tasks/take-dump.ts +++ b/packages/contracts/tasks/take-dump.ts @@ -10,7 +10,6 @@ import { remove0x } from '@eth-optimism/core-utils' import { predeploys } from '../src/predeploys' import { getContractFromArtifact } from '../src/deploy-utils' -import { getDeployConfig } from '../src/deploy-config' import { names } from '../src/address-names' task('take-dump').setAction(async (args, hre) => { @@ -30,13 +29,10 @@ task('take-dump').setAction(async (args, hre) => { /* eslint-enable @typescript-eslint/no-var-requires */ - // Make sure we have a deploy config for this network - const deployConfig = getDeployConfig(hre.network.name) - // Basic warning so users know that the whitelist will be disabled if the owner is the zero address. if ( - deployConfig.ovmWhitelistOwner === undefined || - deployConfig.ovmWhitelistOwner === ethers.constants.AddressZero + hre.deployConfig.ovmWhitelistOwner === undefined || + hre.deployConfig.ovmWhitelistOwner === ethers.constants.AddressZero ) { console.log( 'WARNING: whitelist owner is undefined or address(0), whitelist will be disabled' @@ -45,15 +41,15 @@ task('take-dump').setAction(async (args, hre) => { const variables = { OVM_DeployerWhitelist: { - owner: deployConfig.ovmWhitelistOwner, + owner: hre.deployConfig.ovmWhitelistOwner, }, OVM_GasPriceOracle: { - _owner: deployConfig.ovmGasPriceOracleOwner, - gasPrice: deployConfig.gasPriceOracleL2GasPrice, - l1BaseFee: deployConfig.gasPriceOracleL1BaseFee, - overhead: deployConfig.gasPriceOracleOverhead, - scalar: deployConfig.gasPriceOracleScalar, - decimals: deployConfig.gasPriceOracleDecimals, + _owner: hre.deployConfig.ovmGasPriceOracleOwner, + gasPrice: hre.deployConfig.gasPriceOracleL2GasPrice, + l1BaseFee: hre.deployConfig.gasPriceOracleL1BaseFee, + overhead: hre.deployConfig.gasPriceOracleOverhead, + scalar: hre.deployConfig.gasPriceOracleScalar, + decimals: hre.deployConfig.gasPriceOracleDecimals, }, L2StandardBridge: { l1TokenBridge: ( @@ -65,7 +61,7 @@ task('take-dump').setAction(async (args, hre) => { messenger: predeploys.L2CrossDomainMessenger, }, OVM_SequencerFeeVault: { - l1FeeWallet: deployConfig.ovmFeeWalletAddress, + l1FeeWallet: hre.deployConfig.ovmFeeWalletAddress, }, OVM_ETH: { l2Bridge: predeploys.L2StandardBridge, @@ -135,7 +131,7 @@ task('take-dump').setAction(async (args, hre) => { const genesis = { commit, config: { - chainId: deployConfig.l2ChainId, + chainId: hre.deployConfig.l2ChainId, homesteadBlock: 0, eip150Block: 0, eip155Block: 0, @@ -145,18 +141,18 @@ task('take-dump').setAction(async (args, hre) => { petersburgBlock: 0, istanbulBlock: 0, muirGlacierBlock: 0, - berlinBlock: deployConfig.hfBerlinBlock, + berlinBlock: hre.deployConfig.hfBerlinBlock, clique: { period: 0, epoch: 30000, }, }, difficulty: '1', - gasLimit: deployConfig.l2BlockGasLimit.toString(10), + gasLimit: hre.deployConfig.l2BlockGasLimit.toString(10), extradata: '0x' + '00'.repeat(32) + - remove0x(deployConfig.ovmBlockSignerAddress) + + remove0x(hre.deployConfig.ovmBlockSignerAddress) + '00'.repeat(65), alloc: dump, } diff --git a/packages/hardhat-deploy-config/.depcheckrc b/packages/hardhat-deploy-config/.depcheckrc new file mode 100644 index 0000000000000..3a691b778839b --- /dev/null +++ b/packages/hardhat-deploy-config/.depcheckrc @@ -0,0 +1,13 @@ +ignores: [ + "@babel/eslint-parser", + "@typescript-eslint/parser", + "eslint-plugin-import", + "eslint-plugin-unicorn", + "eslint-plugin-jsdoc", + "eslint-plugin-prefer-arrow", + "eslint-plugin-react", + "@typescript-eslint/eslint-plugin", + "eslint-config-prettier", + "eslint-plugin-prettier", + "chai" +] diff --git a/packages/hardhat-deploy-config/.eslintrc.js b/packages/hardhat-deploy-config/.eslintrc.js new file mode 100644 index 0000000000000..bfd2057be80bd --- /dev/null +++ b/packages/hardhat-deploy-config/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: '../../.eslintrc.js', +} diff --git a/packages/hardhat-deploy-config/.gitignore b/packages/hardhat-deploy-config/.gitignore new file mode 100644 index 0000000000000..b38db2f296ff3 --- /dev/null +++ b/packages/hardhat-deploy-config/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +build/ diff --git a/packages/hardhat-deploy-config/.lintstagedrc.yml b/packages/hardhat-deploy-config/.lintstagedrc.yml new file mode 100644 index 0000000000000..a3035a2299b26 --- /dev/null +++ b/packages/hardhat-deploy-config/.lintstagedrc.yml @@ -0,0 +1,2 @@ +"*.{ts,js}": + - eslint diff --git a/packages/hardhat-deploy-config/.prettierrc.js b/packages/hardhat-deploy-config/.prettierrc.js new file mode 100644 index 0000000000000..90ef435957fc5 --- /dev/null +++ b/packages/hardhat-deploy-config/.prettierrc.js @@ -0,0 +1,3 @@ +module.exports = { + ...require('../../.prettierrc.js'), +}; diff --git a/packages/hardhat-deploy-config/LICENSE b/packages/hardhat-deploy-config/LICENSE new file mode 100644 index 0000000000000..6a7da5218bb25 --- /dev/null +++ b/packages/hardhat-deploy-config/LICENSE @@ -0,0 +1,22 @@ +(The MIT License) + +Copyright 2020-2021 Optimism + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/hardhat-deploy-config/README.md b/packages/hardhat-deploy-config/README.md new file mode 100644 index 0000000000000..adf5553d22111 --- /dev/null +++ b/packages/hardhat-deploy-config/README.md @@ -0,0 +1,9 @@ +# @eth-optimism/hardhat-deploy-config + +`hardhat-deploy-config` is a simple plugin that adds support for global deploy configuration values. + +## Installation + +``` +yarn add @eth-optimism/hardhat-deploy-config +``` diff --git a/packages/hardhat-deploy-config/package.json b/packages/hardhat-deploy-config/package.json new file mode 100644 index 0000000000000..2e94ef3bd6c70 --- /dev/null +++ b/packages/hardhat-deploy-config/package.json @@ -0,0 +1,39 @@ +{ + "name": "@eth-optimism/hardhat-deploy-config", + "version": "0.1.0", + "description": "[Optimism] Hardhat deploy configuration plugin", + "main": "dist/src/index.js", + "types": "dist/src/index.d.ts", + "files": [ + "dist/*" + ], + "scripts": { + "test:coverage": "echo 'No tests defined.'", + "build": "tsc -p tsconfig.json", + "clean": "rimraf dist/ ./tsconfig.tsbuildinfo", + "lint": "yarn lint:fix && yarn lint:check", + "pre-commit": "lint-staged", + "lint:fix": "yarn lint:check --fix", + "lint:check": "eslint . --max-warnings=0" + }, + "keywords": [ + "optimism", + "ethereum", + "hardhat", + "deploy", + "config", + "plugin" + ], + "homepage": "https://github.com/ethereum-optimism/optimism/tree/develop/packages/hardhat-deploy-config#readme", + "license": "MIT", + "author": "Optimism PBC", + "repository": { + "type": "git", + "url": "https://github.com/ethereum-optimism/optimism.git" + }, + "devDependencies": { + "ethers": "^5.6.8", + "ts-node": "^10.7.0", + "hardhat": "^2.9.6" + } +} diff --git a/packages/hardhat-deploy-config/src/index.ts b/packages/hardhat-deploy-config/src/index.ts new file mode 100644 index 0000000000000..be439224e43e3 --- /dev/null +++ b/packages/hardhat-deploy-config/src/index.ts @@ -0,0 +1,2 @@ +import './type-extensions' +import './plugin' diff --git a/packages/hardhat-deploy-config/src/plugin.ts b/packages/hardhat-deploy-config/src/plugin.ts new file mode 100644 index 0000000000000..2e6b11fb79717 --- /dev/null +++ b/packages/hardhat-deploy-config/src/plugin.ts @@ -0,0 +1,108 @@ +import * as path from 'path' + +import { extendEnvironment, extendConfig } from 'hardhat/config' +import { + HardhatConfig, + HardhatRuntimeEnvironment, + HardhatUserConfig, +} from 'hardhat/types' +import { ethers } from 'ethers' + +// From: https://github.com/wighawag/hardhat-deploy/blob/master/src/index.ts#L63-L76 +const normalizePath = ( + config: HardhatConfig, + userPath: string | undefined, + defaultPath: string +): string => { + if (userPath === undefined) { + userPath = path.join(config.paths.root, defaultPath) + } else { + if (!path.isAbsolute(userPath)) { + userPath = path.normalize(path.join(config.paths.root, userPath)) + } + } + return userPath +} + +export const loadDeployConfig = (hre: HardhatRuntimeEnvironment): any => { + let config: any + try { + config = + // eslint-disable-next-line @typescript-eslint/no-var-requires + require(`${hre.config.paths.deployConfig}/${hre.network.name}.ts`).default + } catch (err) { + throw new Error( + `error while loading deploy config for network: ${hre.network.name}, ${err}` + ) + } + + return new Proxy(parseDeployConfig(hre, config), { + get: (target, prop) => { + if (target.hasOwnProperty(prop)) { + return target[prop] + } + + // Explicitly throw if the property is not found since I can't yet figure out a good way to + // handle the necessary typings. + throw new Error( + `property does not exist in deploy config: ${String(prop)}` + ) + }, + }) +} + +export const parseDeployConfig = ( + hre: HardhatRuntimeEnvironment, + config: any +): any => { + // Create a clone of the config object. Shallow clone is fine because none of the input options + // are expected to be objects or functions etc. + const parsed = { ...config } + + // If the deployConfigSpec is not provided, do no validation + if (!hre.config.deployConfigSpec) { + return parsed + } + + for (const [key, spec] of Object.entries(hre.config.deployConfigSpec)) { + // Make sure the value is defined, or use a default. + if (parsed[key] === undefined) { + if ('default' in spec) { + parsed[key] = spec.default + } else { + throw new Error( + `deploy config is missing required field: ${key} (${spec.type})` + ) + } + } else { + // Make sure the default has the correct type. + if (spec.type === 'address') { + if (!ethers.utils.isAddress(parsed[key])) { + throw new Error( + `deploy config field: ${key} is not of type ${spec.type}: ${parsed[key]}` + ) + } + } else if (typeof parsed[key] !== spec.type) { + throw new Error( + `deploy config field: ${key} is not of type ${spec.type}: ${parsed[key]}` + ) + } + } + } + + return parsed +} + +extendConfig( + (config: HardhatConfig, userConfig: Readonly) => { + config.paths.deployConfig = normalizePath( + config, + userConfig.paths.deployConfig, + 'deploy-config' + ) + } +) + +extendEnvironment((hre) => { + hre.deployConfig = loadDeployConfig(hre) +}) diff --git a/packages/hardhat-deploy-config/src/type-extensions.ts b/packages/hardhat-deploy-config/src/type-extensions.ts new file mode 100644 index 0000000000000..c389d126014c1 --- /dev/null +++ b/packages/hardhat-deploy-config/src/type-extensions.ts @@ -0,0 +1,36 @@ +import 'hardhat/types/runtime' +import 'hardhat/types/config' + +interface DeployConfigSpec { + [key: string]: { + type: 'address' | 'number' | 'string' | 'boolean' + default?: any + } +} + +declare module 'hardhat/types/config' { + interface HardhatUserConfig { + deployConfigSpec?: DeployConfigSpec + } + + interface HardhatConfig { + deployConfigSpec?: DeployConfigSpec + } + + interface ProjectPathsUserConfig { + deployConfig?: string + } + + interface ProjectPathsConfig { + deployConfig?: string + } +} + +declare module 'hardhat/types/runtime' { + interface HardhatRuntimeEnvironment { + deployConfig: { + // TODO: Is there any good way to type this? + [key: string]: any + } + } +} diff --git a/packages/hardhat-deploy-config/tsconfig.json b/packages/hardhat-deploy-config/tsconfig.json new file mode 100644 index 0000000000000..c446a2f3505b4 --- /dev/null +++ b/packages/hardhat-deploy-config/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist" + }, + "include": [ + "src/**/*" + ] +}