import assert from 'assert'

import { task, types } from 'hardhat/config'
import '@nomiclabs/hardhat-ethers'
import 'hardhat-deploy'
import { HardhatRuntimeEnvironment } from 'hardhat/types'
import { Contract, providers, Wallet, Signer, BigNumber } from 'ethers'

import { predeploys } from '../src'

// expectedSemver is the semver version of the contracts
// deployed at bedrock deployment
const expectedSemver = '1.0.0'
const implSlot =
  '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc'
const adminSlot =
  '0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103'
const prefix = '0x420000000000000000000000000000000000'

const logLoud = () => {
  console.log('   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!')
}

const yell = (msg: string) => {
  logLoud()
  console.log(msg)
  logLoud()
}

// checkPredeploys will ensure that all of the predeploys are set
const checkPredeploys = async (
  hre: HardhatRuntimeEnvironment,
  provider: providers.Provider
) => {
  console.log('Checking predeploys are configured correctly')
  const admin = hre.ethers.utils.hexConcat([
    '0x000000000000000000000000',
    predeploys.ProxyAdmin,
  ])

  // We only check predeploys 0x420..00 through 0x420..FF on the mainnet network to
  // reduce the probability of an RPC timeout. After the migration, all predeploys
  // from 0x420..00 through 0x420..07FF should be checked.
  const maxCheck = hre.network.name === 'mainnet' ? 256 : 2048

  const codeReq = []
  const slotReq = []
  // First loop for requests
  for (let i = 0; i < maxCheck; i++) {
    const num = hre.ethers.utils.hexZeroPad('0x' + i.toString(16), 2)
    const addr = hre.ethers.utils.getAddress(
      hre.ethers.utils.hexConcat([prefix, num])
    )

    codeReq.push(provider.getCode(addr))
    slotReq.push(provider.getStorageAt(addr, adminSlot))
  }

  // Wait for all requests to finish
  // The `JsonRpcBatchProvider` will batch requests in the background.
  const codeRes = await Promise.all(codeReq)
  const slotRes = await Promise.all(slotReq)

  // Second loop for response checks
  for (let i = 0; i < maxCheck; i++) {
    const num = hre.ethers.utils.hexZeroPad('0x' + i.toString(16), 2)
    const addr = hre.ethers.utils.getAddress(
      hre.ethers.utils.hexConcat([prefix, num])
    )

    if (codeRes[i] === '0x') {
      throw new Error(`no code found at ${addr}`)
    }

    if (
      addr === predeploys.GovernanceToken ||
      addr === predeploys.ProxyAdmin ||
      addr === predeploys.WETH9
    ) {
      continue
    }

    if (slotRes[i] !== admin) {
      throw new Error(`incorrect admin slot in ${addr}`)
    }

    if (i % (maxCheck / 4) === 0) {
      console.log(`Checked through ${addr}`)
    }
  }
}

// assertSemver will ensure that the semver is the correct version
const assertSemver = async (
  contract: Contract,
  name: string,
  override?: string
) => {
  const version = await contract.version()
  let target = expectedSemver
  if (override) {
    target = override
  }
  if (version !== target) {
    throw new Error(
      `${name}: version mismatch. Got ${version}, expected ${target}`
    )
  }
  console.log(`  - version: ${version}`)
}

// checkProxy will print out the proxy slots
const checkProxy = async (
  _hre: HardhatRuntimeEnvironment,
  name: string,
  provider: providers.Provider
) => {
  const address = predeploys[name]
  if (!address) {
    throw new Error(`unknown contract name: ${name}`)
  }

  const impl = await provider.getStorageAt(address, implSlot)
  const admin = await provider.getStorageAt(address, adminSlot)

  console.log(`  - EIP-1967 implementation slot: ${impl}`)
  console.log(`  - EIP-1967 admin slot: ${admin}`)
}

// assertProxy will require the proxy is set
const assertProxy = async (
  hre: HardhatRuntimeEnvironment,
  name: string,
  provider: providers.Provider
) => {
  const address = predeploys[name]
  if (!address) {
    throw new Error(`unknown contract name: ${name}`)
  }

  const code = await provider.getCode(address)
  const deployInfo = await hre.artifacts.readArtifact('Proxy')

  if (code !== deployInfo.deployedBytecode) {
    throw new Error(`${address}: code mismatch`)
  }

  const impl = await provider.getStorageAt(address, implSlot)
  const implAddress = '0x' + impl.slice(26)
  const implCode = await provider.getCode(implAddress)
  if (implCode === '0x') {
    throw new Error('No code at implementation')
  }
}

// checks to make sure that the genesis magic value
// was set correctly
const checkGenesisMagic = async (
  hre: HardhatRuntimeEnvironment,
  l2Provider: providers.Provider,
  args
) => {
  const magic = '0x' + Buffer.from('BEDROCK').toString('hex')
  let startingBlockNumber: number

  // We have a connection to the L1 chain, fetch the remote value
  if (args.l1RpcUrl !== '') {
    const l1Provider = new hre.ethers.providers.StaticJsonRpcProvider(
      args.l1RpcUrl
    )

    // Get the address if it was passed in via CLI
    // Otherwise get the address from a hardhat deployment file
    let address: string
    if (args.l2OutputOracleAddress !== '') {
      address = args.l2OutputOracleAddress
    } else {
      const Deployment__L2OutputOracle = await hre.deployments.get(
        'L2OutputOracleProxy'
      )
      address = Deployment__L2OutputOracle.address
    }

    const abi = {
      inputs: [],
      name: 'startingBlockNumber',
      outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
      stateMutability: 'view',
      type: 'function',
    }

    const L2OutputOracle = new hre.ethers.Contract(address, [abi], l1Provider)

    // In the migration, the L2OutputOracle proxy is not yet initialized when we
    // want to run this script. Fall back on the local config if we get an error
    // fetching the starting block number.
    try {
      startingBlockNumber = await L2OutputOracle.startingBlockNumber()
    } catch (e) {
      console.log(`Error fetching startingBlockNumber:\n${e.message}`)
      console.log('Falling back to local config.')
      startingBlockNumber = hre.deployConfig.l2OutputOracleStartingBlockNumber
    }
  } else {
    // We do not have a connection to the L1 chain, use the local config
    // The `--network` flag must be set to the L1 network
    startingBlockNumber = hre.deployConfig.l2OutputOracleStartingBlockNumber
  }

  // ensure that the starting block number is a number
  startingBlockNumber = BigNumber.from(startingBlockNumber).toNumber()

  const block = await l2Provider.getBlock(startingBlockNumber)
  const extradata = block.extraData

  if (extradata !== magic) {
    throw new Error(
      `magic value in extradata does not match: got ${extradata}, expected ${magic}`
    )
  }
}

const check = {
  // LegacyMessagePasser
  // - check version
  // - is behind a proxy
  LegacyMessagePasser: async (
    hre: HardhatRuntimeEnvironment,
    signer: Signer
  ) => {
    const LegacyMessagePasser = await hre.ethers.getContractAt(
      'LegacyMessagePasser',
      predeploys.LegacyMessagePasser,
      signer
    )

    await assertSemver(LegacyMessagePasser, 'LegacyMessagePasser')
    await checkProxy(hre, 'LegacyMessagePasser', signer.provider)
    await assertProxy(hre, 'LegacyMessagePasser', signer.provider)
  },
  // DeployerWhitelist
  // - check version
  // - is behind a proxy
  // - owner is `address(0)`
  DeployerWhitelist: async (hre: HardhatRuntimeEnvironment, signer: Signer) => {
    const DeployerWhitelist = await hre.ethers.getContractAt(
      'DeployerWhitelist',
      predeploys.DeployerWhitelist,
      signer
    )

    await assertSemver(DeployerWhitelist, 'DeployerWhitelist')

    const owner = await DeployerWhitelist.owner()
    assert(owner === hre.ethers.constants.AddressZero)
    console.log(`  - owner: ${owner}`)

    await checkProxy(hre, 'DeployerWhitelist', signer.provider)
    await assertProxy(hre, 'DeployerWhitelist', signer.provider)
  },
  // L2CrossDomainMessenger
  // - check version
  // - check OTHER_MESSENGER
  // - check l1CrossDomainMessenger (legacy)
  // - is behind a proxy
  // - check owner
  // - check initialized
  L2CrossDomainMessenger: async (
    hre: HardhatRuntimeEnvironment,
    signer: Signer
  ) => {
    const L2CrossDomainMessenger = await hre.ethers.getContractAt(
      'L2CrossDomainMessenger',
      predeploys.L2CrossDomainMessenger,
      signer
    )

    await assertSemver(
      L2CrossDomainMessenger,
      'L2CrossDomainMessenger',
      '1.4.0'
    )

    const xDomainMessageSenderSlot = await signer.provider.getStorageAt(
      predeploys.L2CrossDomainMessenger,
      204
    )

    const xDomainMessageSender = '0x' + xDomainMessageSenderSlot.slice(26)
    assert(
      xDomainMessageSender === '0x000000000000000000000000000000000000dead'
    )

    const otherMessenger = await L2CrossDomainMessenger.OTHER_MESSENGER()
    assert(otherMessenger !== hre.ethers.constants.AddressZero)
    yell(`  - OTHER_MESSENGER: ${otherMessenger}`)

    const l1CrossDomainMessenger =
      await L2CrossDomainMessenger.l1CrossDomainMessenger()
    yell(`  - l1CrossDomainMessenger: ${l1CrossDomainMessenger}`)

    await checkProxy(hre, 'L2CrossDomainMessenger', signer.provider)
    await assertProxy(hre, 'L2CrossDomainMessenger', signer.provider)

    const MESSAGE_VERSION = await L2CrossDomainMessenger.MESSAGE_VERSION()
    console.log(`  - MESSAGE_VERSION: ${MESSAGE_VERSION}`)
    const MIN_GAS_CALLDATA_OVERHEAD =
      await L2CrossDomainMessenger.MIN_GAS_CALLDATA_OVERHEAD()
    console.log(`  - MIN_GAS_CALLDATA_OVERHEAD: ${MIN_GAS_CALLDATA_OVERHEAD}`)
    const RELAY_CONSTANT_OVERHEAD =
      await L2CrossDomainMessenger.RELAY_CONSTANT_OVERHEAD()
    console.log(`  - RELAY_CONSTANT_OVERHEAD: ${RELAY_CONSTANT_OVERHEAD}`)
    const MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR =
      await L2CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR()
    console.log(
      `  - MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR: ${MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR}`
    )
    const MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR =
      await L2CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR()
    console.log(
      `  - MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR: ${MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR}`
    )
    const RELAY_CALL_OVERHEAD =
      await L2CrossDomainMessenger.RELAY_CALL_OVERHEAD()
    console.log(`  - RELAY_CALL_OVERHEAD: ${RELAY_CALL_OVERHEAD}`)
    const RELAY_RESERVED_GAS = await L2CrossDomainMessenger.RELAY_RESERVED_GAS()
    console.log(`  - RELAY_RESERVED_GAS: ${RELAY_RESERVED_GAS}`)
    const RELAY_GAS_CHECK_BUFFER =
      await L2CrossDomainMessenger.RELAY_GAS_CHECK_BUFFER()
    console.log(`  - RELAY_GAS_CHECK_BUFFER: ${RELAY_GAS_CHECK_BUFFER}`)

    const slot = await signer.provider.getStorageAt(
      predeploys.L2CrossDomainMessenger,
      0
    )

    const spacer = '0x' + slot.slice(26)
    console.log(`  - legacy spacer: ${spacer}`)

    const initialized = '0x' + slot.slice(24, 26)
    assert(initialized === '0x01')
    console.log(`  - initialized: ${initialized}`)
  },
  // GasPriceOracle
  // - check version
  // - check decimals
  GasPriceOracle: async (hre: HardhatRuntimeEnvironment, signer: Signer) => {
    const GasPriceOracle = await hre.ethers.getContractAt(
      'GasPriceOracle',
      predeploys.GasPriceOracle,
      signer
    )

    await assertSemver(GasPriceOracle, 'GasPriceOracle')

    const decimals = await GasPriceOracle.decimals()
    assert(decimals.eq(6))
    console.log(`  - decimals: ${decimals.toNumber()}`)

    await checkProxy(hre, 'GasPriceOracle', signer.provider)
    await assertProxy(hre, 'GasPriceOracle', signer.provider)
  },
  // L2StandardBridge
  // - check version
  L2StandardBridge: async (hre: HardhatRuntimeEnvironment, signer: Signer) => {
    const L2StandardBridge = await hre.ethers.getContractAt(
      'L2StandardBridge',
      predeploys.L2StandardBridge,
      signer
    )

    await assertSemver(L2StandardBridge, 'L2StandardBridge', '1.1.0')

    const OTHER_BRIDGE = await L2StandardBridge.OTHER_BRIDGE()
    assert(OTHER_BRIDGE !== hre.ethers.constants.AddressZero)
    yell(`  - OTHER_BRIDGE: ${OTHER_BRIDGE}`)

    const MESSENGER = await L2StandardBridge.MESSENGER()
    assert(MESSENGER === predeploys.L2CrossDomainMessenger)

    await checkProxy(hre, 'L2StandardBridge', signer.provider)
    await assertProxy(hre, 'L2StandardBridge', signer.provider)
  },
  // SequencerFeeVault
  // - check version
  // - check RECIPIENT
  // - check l1FeeWallet (legacy)
  SequencerFeeVault: async (hre: HardhatRuntimeEnvironment, signer: Signer) => {
    const SequencerFeeVault = await hre.ethers.getContractAt(
      'SequencerFeeVault',
      predeploys.SequencerFeeVault,
      signer
    )

    await assertSemver(SequencerFeeVault, 'SequencerFeeVault', '1.2.0')

    const RECIPIENT = await SequencerFeeVault.RECIPIENT()
    assert(RECIPIENT !== hre.ethers.constants.AddressZero)
    yell(`  - RECIPIENT: ${RECIPIENT}`)

    const l1FeeWallet = await SequencerFeeVault.l1FeeWallet()
    assert(l1FeeWallet !== hre.ethers.constants.AddressZero)
    console.log(`  - l1FeeWallet: ${l1FeeWallet}`)

    const MIN_WITHDRAWAL_AMOUNT =
      await SequencerFeeVault.MIN_WITHDRAWAL_AMOUNT()
    console.log(`  - MIN_WITHDRAWAL_AMOUNT: ${MIN_WITHDRAWAL_AMOUNT}`)

    const WITHDRAWAL_NETWORK = await SequencerFeeVault.WITHDRAWAL_NETWORK()
    assert(WITHDRAWAL_NETWORK < 2)
    console.log(`  - WITHDRAWAL_NETWORK: ${WITHDRAWAL_NETWORK}`)

    await checkProxy(hre, 'SequencerFeeVault', signer.provider)
    await assertProxy(hre, 'SequencerFeeVault', signer.provider)
  },
  // OptimismMintableERC20Factory
  // - check version
  OptimismMintableERC20Factory: async (
    hre: HardhatRuntimeEnvironment,
    signer: Signer
  ) => {
    const OptimismMintableERC20Factory = await hre.ethers.getContractAt(
      'OptimismMintableERC20Factory',
      predeploys.OptimismMintableERC20Factory,
      signer
    )

    await assertSemver(
      OptimismMintableERC20Factory,
      'OptimismMintableERC20Factory',
      '1.1.0'
    )

    const BRIDGE = await OptimismMintableERC20Factory.BRIDGE()
    assert(BRIDGE !== hre.ethers.constants.AddressZero)

    await checkProxy(hre, 'OptimismMintableERC20Factory', signer.provider)
    await assertProxy(hre, 'OptimismMintableERC20Factory', signer.provider)
  },
  // L1BlockNumber
  // - check version
  L1BlockNumber: async (hre: HardhatRuntimeEnvironment, signer: Signer) => {
    const L1BlockNumber = await hre.ethers.getContractAt(
      'L1BlockNumber',
      predeploys.L1BlockNumber,
      signer
    )

    await assertSemver(L1BlockNumber, 'L1BlockNumber')

    await checkProxy(hre, 'L1BlockNumber', signer.provider)
    await assertProxy(hre, 'L1BlockNumber', signer.provider)
  },
  // L1Block
  // - check version
  L1Block: async (hre: HardhatRuntimeEnvironment, signer: Signer) => {
    const L1Block = await hre.ethers.getContractAt(
      'L1Block',
      predeploys.L1Block,
      signer
    )

    await assertSemver(L1Block, 'L1Block')

    await checkProxy(hre, 'L1Block', signer.provider)
    await assertProxy(hre, 'L1Block', signer.provider)
  },
  // WETH9
  // - check name
  // - check symbol
  // - check decimals
  WETH9: async (hre: HardhatRuntimeEnvironment, signer: Signer) => {
    const WETH9 = await hre.ethers.getContractAt(
      'WETH9',
      predeploys.WETH9,
      signer
    )

    const name = await WETH9.name()
    assert(name === 'Wrapped Ether')
    console.log(`  - name: ${name}`)

    const symbol = await WETH9.symbol()
    assert(symbol === 'WETH')
    console.log(`  - symbol: ${symbol}`)

    const decimals = await WETH9.decimals()
    assert(decimals === 18)
    console.log(`  - decimals: ${decimals}`)
  },
  // GovernanceToken
  // - not behind a proxy
  // - check name
  // - check symbol
  // - check owner
  GovernanceToken: async (hre: HardhatRuntimeEnvironment, signer: Signer) => {
    const GovernanceToken = await hre.ethers.getContractAt(
      'GovernanceToken',
      predeploys.GovernanceToken,
      signer
    )

    const name = await GovernanceToken.name()
    assert(name === 'Optimism')
    console.log(`  - name: ${name}`)

    const symbol = await GovernanceToken.symbol()
    assert(symbol === 'OP')
    console.log(`  - symbol: ${symbol}`)

    const owner = await GovernanceToken.owner()
    yell(`  - owner: ${owner}`)

    const totalSupply = await GovernanceToken.totalSupply()
    console.log(`  - totalSupply: ${totalSupply}`)

    await checkProxy(hre, 'GovernanceToken', signer.provider)
    // No proxy at this address, don't call assertProxy
  },
  // L2ERC721Bridge
  // - check version
  L2ERC721Bridge: async (hre: HardhatRuntimeEnvironment, signer: Signer) => {
    const L2ERC721Bridge = await hre.ethers.getContractAt(
      'L2ERC721Bridge',
      predeploys.L2ERC721Bridge,
      signer
    )

    await assertSemver(L2ERC721Bridge, 'L2ERC721Bridge', '1.1.0')

    const MESSENGER = await L2ERC721Bridge.MESSENGER()
    assert(MESSENGER !== hre.ethers.constants.AddressZero)
    console.log(`  - MESSENGER: ${MESSENGER}`)

    const OTHER_BRIDGE = await L2ERC721Bridge.OTHER_BRIDGE()
    assert(OTHER_BRIDGE !== hre.ethers.constants.AddressZero)
    yell(`  - OTHER_BRIDGE: ${OTHER_BRIDGE}`)

    await checkProxy(hre, 'L2ERC721Bridge', signer.provider)
    await assertProxy(hre, 'L2ERC721Bridge', signer.provider)
  },
  // OptimismMintableERC721Factory
  // - check version
  OptimismMintableERC721Factory: async (
    hre: HardhatRuntimeEnvironment,
    signer: Signer
  ) => {
    const OptimismMintableERC721Factory = await hre.ethers.getContractAt(
      'OptimismMintableERC721Factory',
      predeploys.OptimismMintableERC721Factory,
      signer
    )

    await assertSemver(
      OptimismMintableERC721Factory,
      'OptimismMintableERC721Factory',
      '1.2.0'
    )

    const BRIDGE = await OptimismMintableERC721Factory.BRIDGE()
    assert(BRIDGE !== hre.ethers.constants.AddressZero)
    console.log(`  - BRIDGE: ${BRIDGE}`)

    const REMOTE_CHAIN_ID =
      await OptimismMintableERC721Factory.REMOTE_CHAIN_ID()
    assert(REMOTE_CHAIN_ID !== 0)
    console.log(`  - REMOTE_CHAIN_ID: ${REMOTE_CHAIN_ID}`)

    await checkProxy(hre, 'OptimismMintableERC721Factory', signer.provider)
    await assertProxy(hre, 'OptimismMintableERC721Factory', signer.provider)
  },
  // ProxyAdmin
  // - check owner
  ProxyAdmin: async (hre: HardhatRuntimeEnvironment, signer: Signer) => {
    const ProxyAdmin = await hre.ethers.getContractAt(
      'ProxyAdmin',
      predeploys.ProxyAdmin,
      signer
    )

    const owner = await ProxyAdmin.owner()
    assert(owner !== hre.ethers.constants.AddressZero)
    yell(`  - owner: ${owner}`)

    const addressManager = await ProxyAdmin.addressManager()
    console.log(`  - addressManager: ${addressManager}`)

    await checkProxy(hre, 'ProxyAdmin', signer.provider)
    await assertProxy(hre, 'ProxyAdmin', signer.provider)
  },
  // BaseFeeVault
  // - check version
  // - check MIN_WITHDRAWAL_AMOUNT
  // - check RECIPIENT
  BaseFeeVault: async (hre: HardhatRuntimeEnvironment, signer: Signer) => {
    const BaseFeeVault = await hre.ethers.getContractAt(
      'BaseFeeVault',
      predeploys.BaseFeeVault,
      signer
    )

    await assertSemver(BaseFeeVault, 'BaseFeeVault', '1.2.0')

    const MIN_WITHDRAWAL_AMOUNT = await BaseFeeVault.MIN_WITHDRAWAL_AMOUNT()
    console.log(`  - MIN_WITHDRAWAL_AMOUNT: ${MIN_WITHDRAWAL_AMOUNT}`)

    const RECIPIENT = await BaseFeeVault.RECIPIENT()
    assert(RECIPIENT !== hre.ethers.constants.AddressZero)
    yell(`  - RECIPIENT: ${RECIPIENT}`)

    const WITHDRAWAL_NETWORK = await BaseFeeVault.WITHDRAWAL_NETWORK()
    assert(WITHDRAWAL_NETWORK < 2)
    console.log(`  - WITHDRAWAL_NETWORK: ${WITHDRAWAL_NETWORK}`)

    await checkProxy(hre, 'BaseFeeVault', signer.provider)
    await assertProxy(hre, 'BaseFeeVault', signer.provider)
  },
  // L1FeeVault
  // - check version
  // - check MIN_WITHDRAWAL_AMOUNT
  // - check RECIPIENT
  L1FeeVault: async (hre: HardhatRuntimeEnvironment, signer: Signer) => {
    const L1FeeVault = await hre.ethers.getContractAt(
      'L1FeeVault',
      predeploys.L1FeeVault,
      signer
    )

    await assertSemver(L1FeeVault, 'L1FeeVault', '1.2.0')

    const MIN_WITHDRAWAL_AMOUNT = await L1FeeVault.MIN_WITHDRAWAL_AMOUNT()
    console.log(`  - MIN_WITHDRAWAL_AMOUNT: ${MIN_WITHDRAWAL_AMOUNT}`)

    const RECIPIENT = await L1FeeVault.RECIPIENT()
    assert(RECIPIENT !== hre.ethers.constants.AddressZero)
    yell(`  - RECIPIENT: ${RECIPIENT}`)

    const WITHDRAWAL_NETWORK = await L1FeeVault.WITHDRAWAL_NETWORK()
    assert(WITHDRAWAL_NETWORK < 2)
    console.log(`  - WITHDRAWAL_NETWORK: ${WITHDRAWAL_NETWORK}`)

    await checkProxy(hre, 'L1FeeVault', signer.provider)
    await assertProxy(hre, 'L1FeeVault', signer.provider)
  },
  // L2ToL1MessagePasser
  // - check version
  L2ToL1MessagePasser: async (
    hre: HardhatRuntimeEnvironment,
    signer: Signer
  ) => {
    const L2ToL1MessagePasser = await hre.ethers.getContractAt(
      'L2ToL1MessagePasser',
      predeploys.L2ToL1MessagePasser,
      signer
    )

    await assertSemver(L2ToL1MessagePasser, 'L2ToL1MessagePasser')

    const MESSAGE_VERSION = await L2ToL1MessagePasser.MESSAGE_VERSION()
    console.log(`  - MESSAGE_VERSION: ${MESSAGE_VERSION}`)

    const messageNonce = await L2ToL1MessagePasser.messageNonce()
    console.log(`  - messageNonce: ${messageNonce}`)

    await checkProxy(hre, 'L2ToL1MessagePasser', signer.provider)
    await assertProxy(hre, 'L2ToL1MessagePasser', signer.provider)
  },
}

task('check-l2', 'Checks a freshly migrated L2 system for correct migration')
  .addOptionalParam('l1RpcUrl', 'L1 RPC URL of node', '', types.string)
  .addOptionalParam('l2RpcUrl', 'L2 RPC URL of node', '', types.string)
  .addOptionalParam('chainId', 'Expected chain id', 0, types.int)
  .addOptionalParam(
    'l2OutputOracleAddress',
    'Address of the L2OutputOracle oracle',
    '',
    types.string
  )
  .addOptionalParam(
    'skipPredeployCheck',
    'Skip long check',
    false,
    types.boolean
  )
  .setAction(async (args, hre: HardhatRuntimeEnvironment) => {
    yell('Manually check values wrapped in !!!!')
    console.log()

    let signer: Signer = hre.ethers.provider.getSigner()

    if (args.l2RpcUrl !== '') {
      console.log('Using CLI URL for provider instead of hardhat network')
      const provider = new hre.ethers.providers.JsonRpcBatchProvider(
        args.l2RpcUrl
      )
      signer = Wallet.createRandom().connect(provider)
    }

    if (args.chainId !== 0) {
      const chainId = await signer.getChainId()
      if (chainId !== args.chainId) {
        throw new Error(
          `Unexpected Chain ID. Got ${chainId}, expected ${args.chainId}`
        )
      }
      console.log(`Verified Chain ID: ${chainId}`)
    } else {
      console.log(`Skipping Chain ID validation...`)
    }

    // Ensure that all the predeploys exist, including the not
    // currently configured ones
    if (!args.skipPredeployCheck) {
      await checkPredeploys(hre, signer.provider)
    }

    await checkGenesisMagic(hre, signer.provider, args)

    console.log()
    // Check the currently configured predeploys
    for (const [name, fn] of Object.entries(check)) {
      const address = predeploys[name]
      console.log(`${name}: ${address}`)
      await fn(hre, signer)
    }
  })
