Commit a320e744 authored by smartcontracts's avatar smartcontracts Committed by GitHub

feat(ctp): add deploy configuration for NFT bridge (#2780)

* contracts: deploy scripts

* deploy-scripts: rename

* wip: deploy script

* feat: use deploy config plugin
Co-authored-by: default avatarMark Tyneway <mark.tyneway@gmail.com>
parent ff0723aa
---
'@eth-optimism/hardhat-deploy-config': patch
---
Properly exports DeployConfigSpec type
---
'@eth-optimism/contracts-periphery': patch
---
Updates contracts-periphery to use the standardized hardhat deploy config plugin
import { DeployConfig } from '../../src'
const config: DeployConfig = {
ddd: '0x9C6373dE60c2D3297b18A8f964618ac46E011B58',
}
export default config
...@@ -2,21 +2,21 @@ ...@@ -2,21 +2,21 @@
import { DeployFunction } from 'hardhat-deploy/dist/types' import { DeployFunction } from 'hardhat-deploy/dist/types'
const deployFn: DeployFunction = async (hre) => { const deployFn: DeployFunction = async (hre) => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const Artifact__L1CrossDomainMessenger = require(`@eth-optimism/contracts/deployments/${hre.network.name}/Proxy__OVM_L1CrossDomainMessenger.json`)
const { deployer } = await hre.getNamedAccounts() const { deployer } = await hre.getNamedAccounts()
const L2ERC721Bridge = await hre.companionNetworks['l2'].deployments.get(
'L2ERC721Bridge'
)
await hre.deployments.deploy('L1ERC721Bridge', { const { deploy } = await hre.deployments.deterministic('ProxyAdmin', {
salt: hre.ethers.utils.solidityKeccak256(
['string'],
['PeripheryProxyAdmin']
),
from: deployer, from: deployer,
args: [Artifact__L1CrossDomainMessenger.address, L2ERC721Bridge.address], args: [hre.deployConfig.ddd],
log: true, log: true,
}) })
await deploy()
} }
deployFn.tags = ['L1ERC721Bridge'] deployFn.tags = ['PeripheryProxyAdmin']
export default deployFn export default deployFn
/* Imports: External */ /* Imports: External */
import { DeployFunction } from 'hardhat-deploy/dist/types' import { DeployFunction } from 'hardhat-deploy/dist/types'
import { getDeployConfig } from '../src'
const deployFn: DeployFunction = async (hre) => { const deployFn: DeployFunction = async (hre) => {
const { deployer } = await hre.getNamedAccounts() const { deployer } = await hre.getNamedAccounts()
const config = getDeployConfig(hre.network.name)
const { deploy } = await hre.deployments.deterministic('AssetReceiver', { const { deploy } = await hre.deployments.deterministic('AssetReceiver', {
salt: hre.ethers.utils.solidityKeccak256(['string'], ['RetroReceiver']), salt: hre.ethers.utils.solidityKeccak256(['string'], ['RetroReceiver']),
from: deployer, from: deployer,
args: [config.ddd], args: [hre.deployConfig.ddd],
log: true, log: true,
}) })
......
/* Imports: External */ /* Imports: External */
import { DeployFunction } from 'hardhat-deploy/dist/types' import { DeployFunction } from 'hardhat-deploy/dist/types'
import { getDeployConfig } from '../src'
const deployFn: DeployFunction = async (hre) => { const deployFn: DeployFunction = async (hre) => {
const { deployer } = await hre.getNamedAccounts() const { deployer } = await hre.getNamedAccounts()
const config = getDeployConfig(hre.network.name)
const { deploy } = await hre.deployments.deterministic( const { deploy } = await hre.deployments.deterministic(
'TeleportrWithdrawer', 'TeleportrWithdrawer',
{ {
...@@ -16,7 +12,7 @@ const deployFn: DeployFunction = async (hre) => { ...@@ -16,7 +12,7 @@ const deployFn: DeployFunction = async (hre) => {
['TeleportrWithdrawer'] ['TeleportrWithdrawer']
), ),
from: deployer, from: deployer,
args: [config.ddd], args: [hre.deployConfig.ddd],
log: true, log: true,
} }
) )
......
/* Imports: External */ /* Imports: External */
import { DeployFunction } from 'hardhat-deploy/dist/types' import { DeployFunction } from 'hardhat-deploy/dist/types'
import { getDeployConfig } from '../../src'
const deployFn: DeployFunction = async (hre) => { const deployFn: DeployFunction = async (hre) => {
const { deployer } = await hre.getNamedAccounts() const { deployer } = await hre.getNamedAccounts()
const config = getDeployConfig(hre.network.name)
const { deploy } = await hre.deployments.deterministic('Drippie', { const { deploy } = await hre.deployments.deterministic('Drippie', {
salt: hre.ethers.utils.solidityKeccak256(['string'], ['Drippie']), salt: hre.ethers.utils.solidityKeccak256(['string'], ['Drippie']),
from: deployer, from: deployer,
args: [config.ddd], args: [hre.deployConfig.ddd],
log: true, log: true,
}) })
......
/* Imports: External */ /* Imports: External */
import { DeployFunction } from 'hardhat-deploy/dist/types' import { DeployFunction } from 'hardhat-deploy/dist/types'
import { ethers } from 'hardhat'
const deployFn: DeployFunction = async (hre) => { const deployFn: DeployFunction = async (hre) => {
const { deployer } = await hre.getNamedAccounts() const { deployer } = await hre.getNamedAccounts()
const L2ERC721Bridge = await hre.deployments.get('L2ERC721Bridge')
await hre.deployments.deploy('L2StandardERC721Factory', { await hre.deployments.deploy('L1ERC721Bridge', {
from: deployer, from: deployer,
args: [L2ERC721Bridge.address], args: [ethers.constants.AddressZero, ethers.constants.AddressZero],
log: true, log: true,
}) })
} }
deployFn.tags = ['L2StandardERC721Factory'] deployFn.tags = ['L1ERC721BridgeImplementation']
deployFn.dependencies = ['L1ERC721BridgeProxy']
export default deployFn export default deployFn
/* Imports: External */
import { DeployFunction } from 'hardhat-deploy/dist/types'
const deployFn: DeployFunction = async (hre) => {
const { deployer } = await hre.getNamedAccounts()
const { deploy } = await hre.deployments.deterministic('Proxy', {
salt: hre.ethers.utils.solidityKeccak256(
['string'],
['L1ERC721BridgeProxy']
),
from: deployer,
args: [hre.deployConfig.ddd],
log: true,
})
await deploy()
}
deployFn.tags = ['L1ERC721BridgeProxy']
export default deployFn
/* Imports: External */ /* Imports: External */
import { DeployFunction } from 'hardhat-deploy/dist/types' import { DeployFunction } from 'hardhat-deploy/dist/types'
import { predeploys } from '@eth-optimism/contracts' import { ethers } from 'hardhat'
const deployFn: DeployFunction = async (hre) => { const deployFn: DeployFunction = async (hre) => {
const { deployer } = await hre.getNamedAccounts() const { deployer } = await hre.getNamedAccounts()
await hre.deployments.deploy('L2ERC721Bridge', { await hre.deployments.deploy('L2ERC721Bridge', {
from: deployer, from: deployer,
args: [predeploys.L2CrossDomainMessenger], args: [ethers.constants.AddressZero, ethers.constants.AddressZero],
log: true, log: true,
}) })
} }
deployFn.tags = ['L2ERC721Bridge'] deployFn.tags = ['L2ERC721BridgeImplementation']
deployFn.dependencies = ['L2ERC721BridgeProxy']
export default deployFn export default deployFn
/* Imports: External */
import { DeployFunction } from 'hardhat-deploy/dist/types'
const deployFn: DeployFunction = async (hre) => {
const { deployer } = await hre.getNamedAccounts()
const { deploy } = await hre.deployments.deterministic('Proxy', {
salt: hre.ethers.utils.solidityKeccak256(
['string'],
['L2ERC721BridgeProxy']
),
from: deployer,
args: [hre.deployConfig.ddd],
log: true,
})
await deploy()
}
deployFn.tags = ['L2ERC721BridgeProxy']
export default deployFn
/* Imports: External */
import { DeployFunction } from 'hardhat-deploy/dist/types'
import { ethers } from 'hardhat'
const deployFn: DeployFunction = async (hre) => {
const { deployer } = await hre.getNamedAccounts()
await hre.deployments.deploy('OptimismMintableERC721Factory', {
from: deployer,
args: [ethers.constants.AddressZero],
log: true,
})
}
deployFn.tags = ['OptimismMintableERC721FactoryImplementation']
deployFn.dependencies = ['OptimismMintableERC721FactoryProxy']
export default deployFn
/* Imports: External */
import { DeployFunction } from 'hardhat-deploy/dist/types'
const deployFn: DeployFunction = async (hre) => {
const { deployer } = await hre.getNamedAccounts()
const { deploy } = await hre.deployments.deterministic('Proxy', {
salt: hre.ethers.utils.solidityKeccak256(
['string'],
['OptimismMintableERC721FactoryProxy']
),
from: deployer,
args: [hre.deployConfig.ddd],
log: true,
})
await deploy()
}
deployFn.tags = ['OptimismMintableERC721FactoryProxy']
export default deployFn
/* Imports: External */
import { Contract } from 'ethers'
import { DeployFunction } from 'hardhat-deploy/dist/types'
import { hexStringEquals, awaitCondition } from '@eth-optimism/core-utils'
import { predeploys } from '@eth-optimism/contracts'
const deployFn: DeployFunction = async (hre) => {
const { deployer } = await hre.getNamedAccounts()
const signer = hre.ethers.provider.getSigner(deployer)
const L1ERC721Bridge = await hre.companionNetworks['l1'].deployments.get(
'L1ERC721Bridge'
)
const Deployment__L2ERC721Bridge = await hre.deployments.get('L2ERC721Bridge')
const L2ERC721Bridge = new Contract(
Deployment__L2ERC721Bridge.address,
Deployment__L2ERC721Bridge.abi,
signer
)
const tx = await L2ERC721Bridge.initialize(L1ERC721Bridge.address)
await tx.wait()
// Ensures that the L2 bridge has been initialized with the correct parameters
await awaitCondition(
async () => {
return (
hexStringEquals(
await L2ERC721Bridge.messenger(),
predeploys.L2CrossDomainMessenger
) &&
hexStringEquals(
await L2ERC721Bridge.l1ERC721Bridge(),
L1ERC721Bridge.address
)
)
},
5000,
100
)
}
deployFn.tags = ['initialize-l2-erc721-bridge']
export default deployFn
...@@ -2,10 +2,13 @@ import { HardhatUserConfig } from 'hardhat/types' ...@@ -2,10 +2,13 @@ import { HardhatUserConfig } from 'hardhat/types'
import { getenv } from '@eth-optimism/core-utils' import { getenv } from '@eth-optimism/core-utils'
import * as dotenv from 'dotenv' import * as dotenv from 'dotenv'
import { configSpec } from './src/config/deploy'
// Hardhat plugins // Hardhat plugins
import '@nomiclabs/hardhat-ethers' import '@nomiclabs/hardhat-ethers'
import '@nomiclabs/hardhat-waffle' import '@nomiclabs/hardhat-waffle'
import '@nomiclabs/hardhat-etherscan' import '@nomiclabs/hardhat-etherscan'
import '@eth-optimism/hardhat-deploy-config'
import 'solidity-coverage' import 'solidity-coverage'
import 'hardhat-gas-reporter' import 'hardhat-gas-reporter'
import 'hardhat-deploy' import 'hardhat-deploy'
...@@ -21,9 +24,6 @@ const config: HardhatUserConfig = { ...@@ -21,9 +24,6 @@ const config: HardhatUserConfig = {
optimism: { optimism: {
chainId: 10, chainId: 10,
url: 'https://mainnet.optimism.io', url: 'https://mainnet.optimism.io',
companionNetworks: {
l1: 'mainnet',
},
verify: { verify: {
etherscan: { etherscan: {
apiKey: getenv('OPTIMISTIC_ETHERSCAN_API_KEY'), apiKey: getenv('OPTIMISTIC_ETHERSCAN_API_KEY'),
...@@ -33,9 +33,6 @@ const config: HardhatUserConfig = { ...@@ -33,9 +33,6 @@ const config: HardhatUserConfig = {
'optimism-kovan': { 'optimism-kovan': {
chainId: 69, chainId: 69,
url: 'https://kovan.optimism.io', url: 'https://kovan.optimism.io',
companionNetworks: {
l1: 'kovan',
},
verify: { verify: {
etherscan: { etherscan: {
apiKey: getenv('OPTIMISTIC_ETHERSCAN_API_KEY'), apiKey: getenv('OPTIMISTIC_ETHERSCAN_API_KEY'),
...@@ -45,9 +42,6 @@ const config: HardhatUserConfig = { ...@@ -45,9 +42,6 @@ const config: HardhatUserConfig = {
ethereum: { ethereum: {
chainId: 1, chainId: 1,
url: `https://mainnet.infura.io/v3/${getenv('INFURA_PROJECT_ID')}`, url: `https://mainnet.infura.io/v3/${getenv('INFURA_PROJECT_ID')}`,
companionNetworks: {
l2: 'optimism',
},
verify: { verify: {
etherscan: { etherscan: {
apiKey: getenv('ETHEREUM_ETHERSCAN_API_KEY'), apiKey: getenv('ETHEREUM_ETHERSCAN_API_KEY'),
...@@ -75,9 +69,6 @@ const config: HardhatUserConfig = { ...@@ -75,9 +69,6 @@ const config: HardhatUserConfig = {
kovan: { kovan: {
chainId: 42, chainId: 42,
url: `https://kovan.infura.io/v3/${getenv('INFURA_PROJECT_ID')}`, url: `https://kovan.infura.io/v3/${getenv('INFURA_PROJECT_ID')}`,
companionNetworks: {
l2: 'optimism-kovan',
},
verify: { verify: {
etherscan: { etherscan: {
apiKey: getenv('ETHEREUM_ETHERSCAN_API_KEY'), apiKey: getenv('ETHEREUM_ETHERSCAN_API_KEY'),
...@@ -85,6 +76,17 @@ const config: HardhatUserConfig = { ...@@ -85,6 +76,17 @@ const config: HardhatUserConfig = {
}, },
}, },
}, },
paths: {
deployConfig: './config/deploy',
},
deployConfigSpec: configSpec,
external: {
contracts: [
{
artifacts: '../contracts-bedrock/artifacts',
},
],
},
mocha: { mocha: {
timeout: 50000, timeout: 50000,
}, },
......
...@@ -56,13 +56,14 @@ ...@@ -56,13 +56,14 @@
"@defi-wonderland/smock": "^2.0.7", "@defi-wonderland/smock": "^2.0.7",
"@eth-optimism/contracts": "^0.5.26", "@eth-optimism/contracts": "^0.5.26",
"@eth-optimism/core-utils": "^0.8.6", "@eth-optimism/core-utils": "^0.8.6",
"@eth-optimism/hardhat-deploy-config": "^0.1.0",
"@ethersproject/hardware-wallets": "^5.6.1", "@ethersproject/hardware-wallets": "^5.6.1",
"@nomiclabs/hardhat-ethers": "^2.0.2", "@nomiclabs/hardhat-ethers": "^2.0.2",
"@nomiclabs/hardhat-etherscan": "^3.0.3", "@nomiclabs/hardhat-etherscan": "^3.0.3",
"@nomiclabs/hardhat-waffle": "^2.0.1", "@nomiclabs/hardhat-waffle": "^2.0.1",
"@rari-capital/solmate": "^6.3.0",
"@openzeppelin/contracts": "4.6.0", "@openzeppelin/contracts": "4.6.0",
"@openzeppelin/contracts-upgradeable": "4.6.0", "@openzeppelin/contracts-upgradeable": "4.6.0",
"@rari-capital/solmate": "^6.3.0",
"@types/chai": "^4.2.18", "@types/chai": "^4.2.18",
"@types/mocha": "^8.2.2", "@types/mocha": "^8.2.2",
"@types/node": "^17.0.21", "@types/node": "^17.0.21",
......
import { ethers } from 'ethers' import { DeployConfigSpec } from '@eth-optimism/hardhat-deploy-config/dist/src/types'
/** /**
* Defines the configuration for a deployment. * Defines the configuration for a deployment.
...@@ -17,75 +17,8 @@ export interface DeployConfig { ...@@ -17,75 +17,8 @@ export interface DeployConfig {
/** /**
* Specification for each of the configuration options. * Specification for each of the configuration options.
*/ */
const configSpec: { export const configSpec: DeployConfigSpec<DeployConfig> = {
[K in keyof DeployConfig]: {
type: string
default?: any
}
} = {
ddd: { ddd: {
type: 'address', type: 'address',
}, },
} }
/**
* Gets the deploy config for the given network.
*
* @param network Network name.
* @returns Deploy config for the given network.
*/
export const getDeployConfig = (network: string): Required<DeployConfig> => {
let config: DeployConfig
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
config = require(`../../config/deploy/${network}.ts`).default
} catch (err) {
throw new Error(
`error while loading deploy config for network: ${network}, ${err}`
)
}
return parseDeployConfig(config)
}
/**
* Parses and validates the given deploy config, replacing any missing values with defaults.
*
* @param config Deploy config to parse.
* @returns Parsed deploy config.
*/
export const parseDeployConfig = (
config: DeployConfig
): Required<DeployConfig> => {
// 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 }
for (const [key, spec] of Object.entries(configSpec)) {
// 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 as Required<DeployConfig>
}
import 'hardhat/types/runtime' import 'hardhat/types/runtime'
import 'hardhat/types/config' import 'hardhat/types/config'
interface DeployConfigSpec { import { DeployConfigSpec } from './types'
[key: string]: {
type: 'address' | 'number' | 'string' | 'boolean'
default?: any
}
}
declare module 'hardhat/types/config' { declare module 'hardhat/types/config' {
interface HardhatUserConfig { interface HardhatUserConfig {
deployConfigSpec?: DeployConfigSpec deployConfigSpec?: DeployConfigSpec<any>
} }
interface HardhatConfig { interface HardhatConfig {
deployConfigSpec?: DeployConfigSpec deployConfigSpec?: DeployConfigSpec<any>
} }
interface ProjectPathsUserConfig { interface ProjectPathsUserConfig {
......
export type DeployConfigSpec<
TDeployConfig extends {
[key: string]: any
}
> = {
[K in keyof TDeployConfig]: {
type: 'address' | 'number' | 'string' | 'boolean'
default?: any
}
}
...@@ -652,6 +652,25 @@ ...@@ -652,6 +652,25 @@
minimatch "^3.1.2" minimatch "^3.1.2"
strip-json-comments "^3.1.1" strip-json-comments "^3.1.1"
"@eth-optimism/common-ts@^0.2.8":
version "0.2.10"
resolved "https://registry.yarnpkg.com/@eth-optimism/common-ts/-/common-ts-0.2.10.tgz#d9867770ff7c0d338bbef4fa598eb4d3f1842f70"
integrity sha512-0Ndl44AL0/ONe6QmSQ4/HenGXk/EkELwKDU6u2IG5IyUfmnXxDJjCGsxOTIfuj5GsMn3EL3fNtNxW6BcTqXWjg==
dependencies:
"@eth-optimism/core-utils" "0.8.6"
"@sentry/node" "^6.3.1"
bcfg "^0.1.7"
commander "^9.0.0"
dotenv "^16.0.0"
envalid "^7.2.2"
ethers "^5.6.8"
express "^4.17.1"
lodash "^4.17.21"
pino "^6.11.3"
pino-multi-stream "^5.3.0"
pino-sentry "^0.7.0"
prom-client "^13.1.0"
"@ethereum-waffle/chai@^3.4.0": "@ethereum-waffle/chai@^3.4.0":
version "3.4.0" version "3.4.0"
resolved "https://registry.yarnpkg.com/@ethereum-waffle/chai/-/chai-3.4.0.tgz#2477877410a96bf370edd64df905b04fb9aba9d5" resolved "https://registry.yarnpkg.com/@ethereum-waffle/chai/-/chai-3.4.0.tgz#2477877410a96bf370edd64df905b04fb9aba9d5"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment