diff --git a/packages/contracts-periphery/deploy/nft-bridge/L1ERC721BridgeImplementation.ts b/packages/contracts-periphery/deploy/nft-bridge/L1ERC721BridgeImplementation.ts index 26262cbe264cb..b01e6f654c7fb 100644 --- a/packages/contracts-periphery/deploy/nft-bridge/L1ERC721BridgeImplementation.ts +++ b/packages/contracts-periphery/deploy/nft-bridge/L1ERC721BridgeImplementation.ts @@ -1,14 +1,110 @@ /* Imports: External */ import { DeployFunction } from 'hardhat-deploy/dist/types' -import { ethers } from 'hardhat' +import { HardhatRuntimeEnvironment } from 'hardhat/types' +import '@eth-optimism/hardhat-deploy-config' +import '@nomiclabs/hardhat-ethers' +import 'hardhat-deploy' +import fetch from 'node-fetch' + +import { + isTargetL1Network, + predeploy, + getProxyAdmin, + validateERC721Bridge, +} from '../../src/nft-bridge-deploy-helpers' + +// Handle the `ops` deployment +const getL1CrossDomainMessengerProxyDeployment = async ( + hre: HardhatRuntimeEnvironment +) => { + const network = hre.network.name + if (network === 'ops-l1') { + const res = await fetch( + 'http://localhost:8080/deployments/local/Proxy__OVM_L1CrossDomainMessenger.json' + ) + return res.json() + } else { + return hre.deployments.get('Proxy__OVM_L1CrossDomainMessenger') + } +} const deployFn: DeployFunction = async (hre) => { const { deployer } = await hre.getNamedAccounts() + const { deploy } = hre.deployments + const { getAddress } = hre.ethers.utils + + if (!isTargetL1Network(hre.network.name)) { + console.log(`Deploying to unsupported network ${hre.network.name}`) + return + } + + console.log(`Deploying L1ERC721Bridge to ${hre.network.name}`) + console.log(`Using deployer ${deployer}`) + + const Deployment__L1ERC721BridgeProxy = await hre.deployments.get( + 'L1ERC721BridgeProxy' + ) + + const L1ERC721BridgeProxy = await hre.ethers.getContractAt( + 'Proxy', + Deployment__L1ERC721BridgeProxy.address + ) + + const admin = await L1ERC721BridgeProxy.callStatic.admin() + if (getAddress(admin) !== getAddress(deployer)) { + throw new Error('deployer is not proxy admin') + } + + // Get the address of the currently deployed L1CrossDomainMessenger. + // This should be the address of the proxy + const Deployment__L1CrossDomainMessengerProxy = + await getL1CrossDomainMessengerProxyDeployment(hre) - await hre.deployments.deploy('L1ERC721Bridge', { + const L1CrossDomainMessengerProxyAddress = + Deployment__L1CrossDomainMessengerProxy.address + + // Deploy the L1ERC721Bridge. The arguments are + // - messenger + // - otherBridge + // Since this is the L1ERC721Bridge, the otherBridge is the + // predeploy address + await deploy('L1ERC721Bridge', { from: deployer, - args: [ethers.constants.AddressZero, ethers.constants.AddressZero], + args: [L1CrossDomainMessengerProxyAddress, predeploy], log: true, + waitConfirmations: 1, + }) + + const Deployment__L1ERC721Bridge = await hre.deployments.get('L1ERC721Bridge') + console.log( + `L1ERC721Bridge deployed to ${Deployment__L1ERC721Bridge.address}` + ) + + await validateERC721Bridge(hre, Deployment__L1ERC721Bridge.address, { + messenger: L1CrossDomainMessengerProxyAddress, + otherBridge: predeploy, + }) + + { + // Upgrade the Proxy to the newly deployed implementation + const tx = await L1ERC721BridgeProxy.upgradeTo( + Deployment__L1ERC721Bridge.address + ) + const receipt = await tx.wait() + console.log(`L1ERC721BridgeProxy upgraded: ${receipt.transactionHash}`) + } + + { + // Set the admin correctly + const newAdmin = getProxyAdmin(hre.network.name) + const tx = await L1ERC721BridgeProxy.changeAdmin(newAdmin) + const receipt = await tx.wait() + console.log(`L1ERC721BridgeProxy admin updated: ${receipt.transactionHash}`) + } + + await validateERC721Bridge(hre, L1ERC721BridgeProxy.address, { + messenger: L1CrossDomainMessengerProxyAddress, + otherBridge: predeploy, }) } diff --git a/packages/contracts-periphery/deploy/nft-bridge/L1ERC721BridgeProxy.ts b/packages/contracts-periphery/deploy/nft-bridge/L1ERC721BridgeProxy.ts index bacbc940c0d50..68fd1f6a0c862 100644 --- a/packages/contracts-periphery/deploy/nft-bridge/L1ERC721BridgeProxy.ts +++ b/packages/contracts-periphery/deploy/nft-bridge/L1ERC721BridgeProxy.ts @@ -1,24 +1,37 @@ /* Imports: External */ import { DeployFunction } from 'hardhat-deploy/dist/types' +import '@eth-optimism/hardhat-deploy-config' +import '@nomiclabs/hardhat-ethers' +import 'hardhat-deploy' + +import { isTargetL1Network } from '../../src/nft-bridge-deploy-helpers' const deployFn: DeployFunction = async (hre) => { const { deployer } = await hre.getNamedAccounts() + const { deploy } = hre.deployments - const { deploy } = await hre.deployments.deterministic( - 'L1ERC721BridgeProxy', - { - contract: 'Proxy', - salt: hre.ethers.utils.solidityKeccak256( - ['string'], - ['L1ERC721BridgeProxy'] - ), - from: deployer, - args: [hre.deployConfig.ddd], - log: true, - } - ) + if (!isTargetL1Network(hre.network.name)) { + console.log(`Deploying to unsupported network ${hre.network.name}`) + return + } + + console.log(`Deploying L1ERC721BridgeProxy to ${hre.network.name}`) + console.log(`Using deployer ${deployer}`) - await deploy() + await deploy('L1ERC721BridgeProxy', { + contract: 'Proxy', + from: deployer, + args: [deployer], + log: true, + waitConfirmations: 1, + }) + + const Deployment__L1ERC721BridgeProxy = await hre.deployments.get( + 'L1ERC721BridgeProxy' + ) + console.log( + `L1ERC721BridgeProxy deployed to ${Deployment__L1ERC721BridgeProxy.address}` + ) } deployFn.tags = ['L1ERC721BridgeProxy'] diff --git a/packages/contracts-periphery/deploy/nft-bridge/L2ERC721BridgeImplementation.ts b/packages/contracts-periphery/deploy/nft-bridge/L2ERC721BridgeImplementation.ts index f353b3ba24bc6..e5bc678492be3 100644 --- a/packages/contracts-periphery/deploy/nft-bridge/L2ERC721BridgeImplementation.ts +++ b/packages/contracts-periphery/deploy/nft-bridge/L2ERC721BridgeImplementation.ts @@ -1,18 +1,95 @@ /* Imports: External */ import { DeployFunction } from 'hardhat-deploy/dist/types' -import { ethers } from 'hardhat' +import '@eth-optimism/hardhat-deploy-config' +import '@nomiclabs/hardhat-ethers' +import 'hardhat-deploy' +import { predeploys } from '@eth-optimism/contracts' + +import { + isTargetL2Network, + predeploy, + validateERC721Bridge, + getProxyAdmin, +} from '../../src/nft-bridge-deploy-helpers' const deployFn: DeployFunction = async (hre) => { const { deployer } = await hre.getNamedAccounts() + const { getAddress } = hre.ethers.utils + + if (!isTargetL2Network(hre.network.name)) { + console.log(`Deploying to unsupported network ${hre.network.name}`) + return + } + + console.log(`Deploying L2ERC721Bridge to ${hre.network.name}`) + console.log(`Using deployer ${deployer}`) + + const L2ERC721BridgeProxy = await hre.ethers.getContractAt('Proxy', predeploy) + + // Check to make sure that the admin of the proxy is the deployer. + // The deployer of the L2ERC721Bridge should be the same as the + // admin of the L2ERC721BridgeProxy so that it is easy to upgrade + // the implementation. The admin is then changed depending on the + // network after the L2ERC721BridgeProxy is upgraded to the implementation + const admin = await L2ERC721BridgeProxy.callStatic.admin() + + if (getAddress(admin) !== getAddress(deployer)) { + throw new Error(`Unexpected admin ${admin}`) + } + + const Deployment__L1ERC721Bridge = await hre.deployments.get( + 'L1ERC721BridgeProxy' + ) + + const L1ERC721BridgeAddress = Deployment__L1ERC721Bridge.address + // Deploy the L2ERC721Bridge implementation await hre.deployments.deploy('L2ERC721Bridge', { from: deployer, - args: [ethers.constants.AddressZero, ethers.constants.AddressZero], + args: [predeploys.L2CrossDomainMessenger, L1ERC721BridgeAddress], log: true, + waitConfirmations: 1, }) + + const Deployment__L2ERC721Bridge = await hre.deployments.get('L2ERC721Bridge') + console.log( + `L2ERC721Bridge deployed to ${Deployment__L2ERC721Bridge.address}` + ) + + await validateERC721Bridge(hre, Deployment__L2ERC721Bridge.address, { + messenger: predeploys.L2CrossDomainMessenger, + otherBridge: L1ERC721BridgeAddress, + }) + + { + // Upgrade the implementation of the proxy to the newly deployed + // L2ERC721Bridge + const tx = await L2ERC721BridgeProxy.upgradeTo( + Deployment__L2ERC721Bridge.address + ) + const receipt = await tx.wait() + console.log( + `Upgraded the implementation of the L2ERC721BridgeProxy: ${receipt.transactionhash}` + ) + } + + await validateERC721Bridge(hre, L2ERC721BridgeProxy.address, { + messenger: predeploys.L2CrossDomainMessenger, + otherBridge: L1ERC721BridgeAddress, + }) + + { + const newAdmin = getProxyAdmin(hre.network.name) + console.log(`Changing admin to ${newAdmin}`) + const tx = await L2ERC721BridgeProxy.changeAdmin(newAdmin) + const receipt = await tx.wait() + console.log( + `Changed admin of the L2ERC721BridgeProxy: ${receipt.transactionHash}` + ) + } } deployFn.tags = ['L2ERC721BridgeImplementation'] -deployFn.dependencies = ['L2ERC721BridgeProxy'] +deployFn.dependencies = ['L2ERC721BridgeProxy', 'L1ERC721BridgeProxy'] export default deployFn diff --git a/packages/contracts-periphery/deploy/nft-bridge/L2ERC721BridgeProxy.ts b/packages/contracts-periphery/deploy/nft-bridge/L2ERC721BridgeProxy.ts index d9c5c9eedb532..1f494b099d654 100644 --- a/packages/contracts-periphery/deploy/nft-bridge/L2ERC721BridgeProxy.ts +++ b/packages/contracts-periphery/deploy/nft-bridge/L2ERC721BridgeProxy.ts @@ -1,24 +1,68 @@ /* Imports: External */ import { DeployFunction } from 'hardhat-deploy/dist/types' +import '@eth-optimism/hardhat-deploy-config' +import '@nomiclabs/hardhat-ethers' +import 'hardhat-deploy' + +const predeploy = '0x4200000000000000000000000000000000000014' const deployFn: DeployFunction = async (hre) => { const { deployer } = await hre.getNamedAccounts() + const { getAddress } = hre.ethers.utils - const { deploy } = await hre.deployments.deterministic( - 'L2ERC721BridgeProxy', - { - contract: 'Proxy', - salt: hre.ethers.utils.solidityKeccak256( - ['string'], - ['L2ERC721BridgeProxy'] - ), - from: deployer, - args: [hre.deployConfig.ddd], - log: true, - } + console.log(`Deploying L2ERC721BridgeProxy to ${hre.network.name}`) + console.log(`Using deployer ${deployer}`) + + // Check to make sure that the Proxy has not been deployed yet + const pre = await hre.ethers.provider.getCode(predeploy, 'latest') + if (pre !== '0x') { + console.log(`Code already deployed to ${predeploy}`) + return + } + + // A special deployer account must be used + const mainnetDeployer = getAddress( + '0x53A6eecC2dD4795Fcc68940ddc6B4d53Bd88Bd9E' + ) + const goerliDeployer = getAddress( + '0x5c679a57e018f5f146838138d3e032ef4913d551' ) + const localDeployer = getAddress('0xdfc82d475833a50de90c642770f34a9db7deb725') + + // Deploy the L2ERC721BridgeProxy as a predeploy address + if (hre.network.name === 'optimism') { + if (getAddress(deployer) !== mainnetDeployer) { + throw new Error(`Incorrect deployer: ${deployer}`) + } + } else if (hre.network.name === 'optimism-goerli') { + if (getAddress(deployer) !== goerliDeployer) { + throw new Error(`Incorrect deployer: ${deployer}`) + } + } else if (hre.network.name === 'ops-l2') { + if (getAddress(deployer) !== localDeployer) { + throw new Error(`Incorrect deployer: ${deployer}`) + } + } else { + throw new Error(`Unknown network: ${hre.network.name}`) + } + + // Set the deployer as the admin of the Proxy. This is + // temporary, the admin will be updated when deploying + // the implementation + await hre.deployments.deploy('L2ERC721BridgeProxy', { + contract: 'Proxy', + from: deployer, + args: [deployer], + log: true, + waitConfirmations: 1, + }) - await deploy() + // Check that the Proxy was deployed to the correct address + const code = await hre.ethers.provider.getCode(predeploy) + if (code === '0x') { + throw new Error('Code is not set at expected predeploy address') + } + console.log(`L2ERC721BridgeProxy deployed to ${predeploy}`) } deployFn.tags = ['L2ERC721BridgeProxy'] diff --git a/packages/contracts-periphery/deploy/nft-bridge/OptimismMintableERC721FactoryImplementation.ts b/packages/contracts-periphery/deploy/nft-bridge/OptimismMintableERC721FactoryImplementation.ts index 03c5cf78cde49..cc57d3229f75f 100644 --- a/packages/contracts-periphery/deploy/nft-bridge/OptimismMintableERC721FactoryImplementation.ts +++ b/packages/contracts-periphery/deploy/nft-bridge/OptimismMintableERC721FactoryImplementation.ts @@ -1,15 +1,82 @@ /* Imports: External */ import { DeployFunction } from 'hardhat-deploy/dist/types' -import { ethers } from 'hardhat' +import '@eth-optimism/hardhat-deploy-config' +import '@nomiclabs/hardhat-ethers' +import 'hardhat-deploy' + +import { getProxyAdmin, predeploy } from '../../src/nft-bridge-deploy-helpers' const deployFn: DeployFunction = async (hre) => { const { deployer } = await hre.getNamedAccounts() + const { getAddress } = hre.ethers.utils + + console.log(`Deploying OptimismMintableERC721Factory to ${hre.network.name}`) + console.log(`Using deployer ${deployer}`) + + const Deployment__OptimismMintableERC721FactoryProxy = + await hre.deployments.get('OptimismMintableERC721FactoryProxy') + + const OptimismMintableERC721FactoryProxy = await hre.ethers.getContractAt( + 'Proxy', + Deployment__OptimismMintableERC721FactoryProxy.address + ) + + // Check that the admin of the OptimismMintableERC721FactoryProxy is the + // deployer. This makes it easy to upgrade the implementation of the proxy + // and then transfer the admin privilege after deploying the implementation + const admin = await OptimismMintableERC721FactoryProxy.callStatic.admin() + if (getAddress(admin) !== getAddress(deployer)) { + throw new Error('deployer is not proxy admin') + } + + let remoteChainId: number + if (hre.network.name === 'optimism') { + remoteChainId = 1 + } else if (hre.network.name === 'optimism-goerli') { + remoteChainId = 5 + } else if (hre.network.name === 'ops-l2') { + remoteChainId = 31337 + } else { + remoteChainId = hre.deployConfig.remoteChainId + } + + if (typeof remoteChainId !== 'number') { + throw new Error('remoteChainId not defined') + } await hre.deployments.deploy('OptimismMintableERC721Factory', { from: deployer, - args: [ethers.constants.AddressZero, 0], + args: [predeploy, remoteChainId], log: true, + waitConfirmations: 1, }) + + const Deployment__OptimismMintableERC721Factory = await hre.deployments.get( + 'OptimismMintableERC721Factory' + ) + console.log( + `OptimismMintableERC721Factory deployed to ${Deployment__OptimismMintableERC721Factory.address}` + ) + + { + // Upgrade the Proxy to the newly deployed implementation + const tx = await OptimismMintableERC721FactoryProxy.upgradeTo( + Deployment__OptimismMintableERC721Factory.address + ) + const receipt = await tx.wait() + console.log( + `OptimismMintableERC721FactoryProxy upgraded: ${receipt.transactionHash}` + ) + } + + { + const newAdmin = getProxyAdmin(hre.network.name) + const tx = await OptimismMintableERC721FactoryProxy.changeAdmin(newAdmin) + const receipt = await tx.wait() + console.log( + `OptimismMintableERC721FactoryProxy admin updated: ${receipt.transactionHash}` + ) + } } deployFn.tags = ['OptimismMintableERC721FactoryImplementation'] diff --git a/packages/contracts-periphery/deploy/nft-bridge/OptimismMintableERC721FactoryProxy.ts b/packages/contracts-periphery/deploy/nft-bridge/OptimismMintableERC721FactoryProxy.ts index 89c8b257c2c7f..9d33385abe819 100644 --- a/packages/contracts-periphery/deploy/nft-bridge/OptimismMintableERC721FactoryProxy.ts +++ b/packages/contracts-periphery/deploy/nft-bridge/OptimismMintableERC721FactoryProxy.ts @@ -1,24 +1,34 @@ /* Imports: External */ import { DeployFunction } from 'hardhat-deploy/dist/types' +import '@eth-optimism/hardhat-deploy-config' +import '@nomiclabs/hardhat-ethers' +import 'hardhat-deploy' const deployFn: DeployFunction = async (hre) => { const { deployer } = await hre.getNamedAccounts() + const { deploy } = hre.deployments - const { deploy } = await hre.deployments.deterministic( - 'OptimismMintableERC721FactoryProxy', - { - contract: 'Proxy', - salt: hre.ethers.utils.solidityKeccak256( - ['string'], - ['OptimismMintableERC721FactoryProxy'] - ), - from: deployer, - args: [hre.deployConfig.ddd], - log: true, - } + console.log( + `Deploying OptimismMintableERC721FactoryProxy to ${hre.network.name}` ) + console.log(`Using deployer ${deployer}`) - await deploy() + // Deploy the OptimismMintableERC721FactoryProxy with + // the deployer as the admin. The admin and implementation + // will be updated with the deployment of the implementation + await deploy('OptimismMintableERC721FactoryProxy', { + contract: 'Proxy', + from: deployer, + args: [deployer], + log: true, + waitConfirmations: 1, + }) + + const Deployment__OptimismMintableERC721FactoryProxy = + await hre.deployments.get('OptimismMintableERC721FactoryProxy') + console.log( + `OptimismMintableERC721FactoryProxy deployed to ${Deployment__OptimismMintableERC721FactoryProxy.address}` + ) } deployFn.tags = ['OptimismMintableERC721FactoryProxy'] diff --git a/packages/contracts-periphery/hardhat.config.ts b/packages/contracts-periphery/hardhat.config.ts index b5e41df964b5c..856d6b28e08c4 100644 --- a/packages/contracts-periphery/hardhat.config.ts +++ b/packages/contracts-periphery/hardhat.config.ts @@ -112,6 +112,21 @@ const config: HardhatUserConfig = { }, }, }, + 'ops-l2': { + chainId: 17, + accounts: [ + '0x3b8d2345102cce2443acb240db6e87c8edd4bb3f821b17fab8ea2c9da08ea132', + '0xa6aecc98b63bafb0de3b29ae9964b14acb4086057808be29f90150214ebd4a0f', + ], + url: 'http://127.0.0.1:8545', + }, + 'ops-l1': { + chainId: 31337, + accounts: [ + '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80', + ], + url: 'http://127.0.0.1:9545', + }, }, paths: { deployConfig: './config/deploy', diff --git a/packages/contracts-periphery/scripts/deploy-nft-bridge.sh b/packages/contracts-periphery/scripts/deploy-nft-bridge.sh new file mode 100755 index 0000000000000..7153e853a2350 --- /dev/null +++ b/packages/contracts-periphery/scripts/deploy-nft-bridge.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +set -e + +L1_NETWORK=ops-l1 +L2_NETWORK=ops-l2 + +# Step 1: deploy the Proxy to the predeploy address on L2 +npx hardhat deploy --tags L2ERC721BridgeProxy --network $L2_NETWORK + +# Step 2: deploy the Proxy for the L1ERC721Bridge to L1 +npx hardhat deploy --tags L1ERC721BridgeProxy --network $L1_NETWORK + +# Step 3: deploy the L2ERC721Bridge implementation +npx hardhat deploy --tags L2ERC721BridgeImplementation --network $L2_NETWORK + +# Step 4: deploy the L1ERC721Bridge implementation to L1 +npx hardhat deploy --tags L1ERC721BridgeImplementation --network $L1_NETWORK + +# Step 5: deploy the Proxy for the OptimismMintableERC721Factory to L2 +npx hardhat deploy --tags OptimismMintableERC721FactoryProxy --network $L2_NETWORK + +# Step 5: deploy the OptimismMintableERC721Factory to L2 +npx hardhat deploy --tags OptimismMintableERC721FactoryImplementation --network $L2_NETWORK diff --git a/packages/contracts-periphery/src/nft-bridge-deploy-helpers.ts b/packages/contracts-periphery/src/nft-bridge-deploy-helpers.ts new file mode 100644 index 0000000000000..84af7642cb475 --- /dev/null +++ b/packages/contracts-periphery/src/nft-bridge-deploy-helpers.ts @@ -0,0 +1,66 @@ +import { utils } from 'ethers' + +// https://optimistic.etherscan.io/address/0x2501c477d0a35545a387aa4a3eee4292a9a8b3f0 +export const l2MainnetMultisig = '0x2501c477D0A35545a387Aa4A3EEe4292A9a8B3F0' +// https://etherscan.io/address/0x9BA6e03D8B90dE867373Db8cF1A58d2F7F006b3A +export const l1MainnetMultisig = '0x9BA6e03D8B90dE867373Db8cF1A58d2F7F006b3A' +// https://goerli.etherscan.io/address/0xf80267194936da1E98dB10bcE06F3147D580a62e +export const goerliAdmin = '0xf80267194936da1E98dB10bcE06F3147D580a62e' +export const predeploy = '0x4200000000000000000000000000000000000014' +export const predeployDeployer = '0xdfc82d475833a50de90c642770f34a9db7deb725' + +export const isTargetL2Network = (network: string): boolean => { + switch (network) { + case 'optimism': + case 'optimism-goerli': + case 'ops-l2': + return true + default: + return false + } +} + +export const isTargetL1Network = (network: string): boolean => { + switch (network) { + case 'mainnet': + case 'goerli': + case 'ops-l1': + return true + default: + return false + } +} + +export const getProxyAdmin = (network: string): string => { + switch (network) { + case 'optimism': + return l2MainnetMultisig + case 'mainnet': + return l1MainnetMultisig + case 'goerli': + case 'optimism-goerli': + return goerliAdmin + case 'ops-l1': + case 'ops-l2': + return predeployDeployer + default: + throw new Error(`unknown network ${network}`) + } +} + +export const validateERC721Bridge = async (hre, address: string, expected) => { + const L1ERC721Bridge = await hre.ethers.getContractAt('ERC721Bridge', address) + + const messenger = await L1ERC721Bridge.messenger() + const otherBridge = await L1ERC721Bridge.otherBridge() + + if (utils.getAddress(messenger) !== utils.getAddress(expected.messenger)) { + throw new Error(`messenger mismatch`) + } + + if ( + utils.getAddress(otherBridge) !== utils.getAddress(expected.otherBridge) + ) { + throw new Error(`otherBridge mismatch`) + } +}