contracts.ts 10.7 KB
Newer Older
1 2
import { getContractInterface, predeploys } from '@eth-optimism/contracts'
import { ethers, Contract } from 'ethers'
3 4 5 6 7 8 9 10 11 12 13 14 15 16
import l1StandardBridge from '@eth-optimism/contracts-bedrock/forge-artifacts/L1StandardBridge.sol/L1StandardBridge.json'
import l2StandardBridge from '@eth-optimism/contracts-bedrock/forge-artifacts/L2StandardBridge.sol/L2StandardBridge.json'
import optimismMintableERC20 from '@eth-optimism/contracts-bedrock/forge-artifacts/OptimismMintableERC20.sol/OptimismMintableERC20.json'
import optimismPortal from '@eth-optimism/contracts-bedrock/forge-artifacts/OptimismPortal.sol/OptimismPortal.json'
import l1CrossDomainMessenger from '@eth-optimism/contracts-bedrock/forge-artifacts/L1CrossDomainMessenger.sol/L1CrossDomainMessenger.json'
import l2CrossDomainMessenger from '@eth-optimism/contracts-bedrock/forge-artifacts/L2CrossDomainMessenger.sol/L2CrossDomainMessenger.json'
import optimismMintableERC20Factory from '@eth-optimism/contracts-bedrock/forge-artifacts/OptimismMintableERC20Factory.sol/OptimismMintableERC20Factory.json'
import proxyAdmin from '@eth-optimism/contracts-bedrock/forge-artifacts/ProxyAdmin.sol/ProxyAdmin.json'
import l2OutputOracle from '@eth-optimism/contracts-bedrock/forge-artifacts/L2OutputOracle.sol/L2OutputOracle.json'
import l1ERC721Bridge from '@eth-optimism/contracts-bedrock/forge-artifacts/L1ERC721Bridge.sol/L1ERC721Bridge.json'
import l2ERC721Bridge from '@eth-optimism/contracts-bedrock/forge-artifacts/L2ERC721Bridge.sol/L2ERC721Bridge.json'
import l1Block from '@eth-optimism/contracts-bedrock/forge-artifacts/L1Block.sol/L1Block.json'
import l2ToL1MessagePasser from '@eth-optimism/contracts-bedrock/forge-artifacts/L2ToL1MessagePasser.sol/L2ToL1MessagePasser.json'
import gasPriceOracle from '@eth-optimism/contracts-bedrock/forge-artifacts/GasPriceOracle.sol/GasPriceOracle.json'
17 18
import disputeGameFactory from '@eth-optimism/contracts-bedrock/forge-artifacts/DisputeGameFactory.sol/DisputeGameFactory.json'
import optimismPortal2 from '@eth-optimism/contracts-bedrock/forge-artifacts/OptimismPortal2.sol/OptimismPortal2.json'
19
import faultDisputeGame from '@eth-optimism/contracts-bedrock/forge-artifacts/FaultDisputeGame.sol/FaultDisputeGame.json'
20 21 22

import { toAddress } from './coercion'
import { DeepPartial } from './type-utils'
23
import { CrossChainMessenger } from '../cross-chain-messenger'
24 25 26 27 28
import { StandardBridgeAdapter, ETHBridgeAdapter } from '../adapters'
import {
  CONTRACT_ADDRESSES,
  DEFAULT_L2_CONTRACT_ADDRESSES,
  BRIDGE_ADAPTER_DATA,
29
  IGNORABLE_CONTRACTS,
30
} from './chain-constants'
31 32 33 34 35 36
import {
  OEContracts,
  OEL1Contracts,
  OEL2Contracts,
  OEContractsLike,
  AddressLike,
37 38
  BridgeAdapters,
  BridgeAdapterData,
39 40 41 42 43 44 45
} from '../interfaces'

/**
 * We've changed some contract names in this SDK to be a bit nicer. Here we remap these nicer names
 * back to the original contract names so we can look them up.
 */
const NAME_REMAPPING = {
46 47 48
  AddressManager: 'Lib_AddressManager' as const,
  OVM_L1BlockNumber: 'iOVM_L1BlockNumber' as const,
  WETH: 'WETH9' as const,
49
  BedrockMessagePasser: 'L2ToL1MessagePasser' as const,
50 51
}

52 53 54
export const getContractInterfaceBedrock = (
  name: string
): ethers.utils.Interface => {
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
  let artifact: any = ''
  switch (name) {
    case 'Lib_AddressManager':
    case 'AddressManager':
      artifact = ''
      break
    case 'L1CrossDomainMessenger':
      artifact = l1CrossDomainMessenger
      break
    case 'L1ERC721Bridge':
      artifact = l1ERC721Bridge
      break
    case 'L2OutputOracle':
      artifact = l2OutputOracle
      break
    case 'OptimismMintableERC20Factory':
      artifact = optimismMintableERC20Factory
      break
    case 'ProxyAdmin':
      artifact = proxyAdmin
      break
    case 'L1StandardBridge':
      artifact = l1StandardBridge
      break
    case 'L2StandardBridge':
      artifact = l2StandardBridge
      break
    case 'OptimismPortal':
      artifact = optimismPortal
      break
    case 'L2CrossDomainMessenger':
      artifact = l2CrossDomainMessenger
      break
    case 'OptimismMintableERC20':
      artifact = optimismMintableERC20
      break
    case 'L2ERC721Bridge':
      artifact = l2ERC721Bridge
      break
    case 'L1Block':
      artifact = l1Block
      break
    case 'L2ToL1MessagePasser':
      artifact = l2ToL1MessagePasser
      break
    case 'GasPriceOracle':
      artifact = gasPriceOracle
      break
103 104 105 106 107 108
    case 'DisputeGameFactory':
      artifact = disputeGameFactory
      break
    case 'OptimismPortal2':
      artifact = optimismPortal2
      break
109 110 111
    case 'FaultDisputeGame':
      artifact = faultDisputeGame
      break
112 113 114 115
  }
  return new ethers.utils.Interface(artifact.abi)
}

116 117
/**
 * Returns an ethers.Contract object for the given name, connected to the appropriate address for
118
 * the given L2 chain ID. Users can also provide a custom address to connect the contract to
119 120 121 122
 * instead. If the chain ID is not known then the user MUST provide a custom address or this
 * function will throw an error.
 *
 * @param contractName Name of the contract to connect to.
123
 * @param l2ChainId Chain ID for the L2 network.
124 125 126 127 128 129 130
 * @param opts Additional options for connecting to the contract.
 * @param opts.address Custom address to connect to the contract.
 * @param opts.signerOrProvider Signer or provider to connect to the contract.
 * @returns An ethers.Contract object connected to the appropriate address and interface.
 */
export const getOEContract = (
  contractName: keyof OEL1Contracts | keyof OEL2Contracts,
131
  l2ChainId: number,
132 133 134 135 136
  opts: {
    address?: AddressLike
    signerOrProvider?: ethers.Signer | ethers.providers.Provider
  } = {}
): Contract => {
137 138 139 140
  // Generally we want to throw an error if a contract address is not provided but there are some
  // exceptions, particularly for contracts that are part of an upgrade that has not yet been
  // deployed. It's OK to not provide an address for these contracts with the caveat that this may
  // cause a runtime error if the contract does actually need to be used.
141
  const addresses = CONTRACT_ADDRESSES[l2ChainId]
142
  if (addresses === undefined && opts.address === undefined) {
143 144 145 146 147 148 149
    if (IGNORABLE_CONTRACTS.includes(contractName)) {
      return undefined
    } else {
      throw new Error(
        `cannot get contract ${contractName} for unknown L2 chain ID ${l2ChainId}, you must provide an address`
      )
    }
150 151
  }

152 153 154 155 156 157 158 159 160 161
  // Bedrock interfaces are backwards compatible. We can prefer Bedrock interfaces over legacy
  // interfaces if they exist.
  const name = NAME_REMAPPING[contractName] || contractName
  let iface: ethers.utils.Interface
  try {
    iface = getContractInterfaceBedrock(name)
  } catch (err) {
    iface = getContractInterface(name)
  }

162 163 164 165
  return new Contract(
    toAddress(
      opts.address || addresses.l1[contractName] || addresses.l2[contractName]
    ),
166
    iface,
167 168 169 170 171
    opts.signerOrProvider
  )
}

/**
172
 * Automatically connects to all contract addresses, both L1 and L2, for the given L2 chain ID. The
173 174 175 176
 * user can provide custom contract address overrides for L1 or L2 contracts. If the given chain ID
 * is not known then the user MUST provide custom contract addresses for ALL L1 contracts or this
 * function will throw an error.
 *
177
 * @param l2ChainId Chain ID for the L2 network.
178 179 180 181 182 183 184 185
 * @param opts Additional options for connecting to the contracts.
 * @param opts.l1SignerOrProvider: Signer or provider to connect to the L1 contracts.
 * @param opts.l2SignerOrProvider: Signer or provider to connect to the L2 contracts.
 * @param opts.overrides Custom contract address overrides for L1 or L2 contracts.
 * @returns An object containing ethers.Contract objects connected to the appropriate addresses on
 * both L1 and L2.
 */
export const getAllOEContracts = (
186
  l2ChainId: number,
187 188 189 190 191 192
  opts: {
    l1SignerOrProvider?: ethers.Signer | ethers.providers.Provider
    l2SignerOrProvider?: ethers.Signer | ethers.providers.Provider
    overrides?: DeepPartial<OEContractsLike>
  } = {}
): OEContracts => {
193
  const addresses = CONTRACT_ADDRESSES[l2ChainId] || {
194 195 196 197 198 199 200
    l1: {
      AddressManager: undefined,
      L1CrossDomainMessenger: undefined,
      L1StandardBridge: undefined,
      StateCommitmentChain: undefined,
      CanonicalTransactionChain: undefined,
      BondManager: undefined,
201 202
      OptimismPortal: undefined,
      L2OutputOracle: undefined,
203 204
      DisputeGameFactory: undefined,
      OptimismPortal2: undefined,
205 206 207 208 209
    },
    l2: DEFAULT_L2_CONTRACT_ADDRESSES,
  }

  // Attach all L1 contracts.
210
  const l1Contracts = {} as OEL1Contracts
211
  for (const [contractName, contractAddress] of Object.entries(addresses.l1)) {
212 213
    l1Contracts[contractName] = getOEContract(
      contractName as keyof OEL1Contracts,
214
      l2ChainId,
215 216 217 218 219
      {
        address: opts.overrides?.l1?.[contractName] || contractAddress,
        signerOrProvider: opts.l1SignerOrProvider,
      }
    )
220 221 222
  }

  // Attach all L2 contracts.
223
  const l2Contracts = {} as OEL2Contracts
224
  for (const [contractName, contractAddress] of Object.entries(addresses.l2)) {
225 226
    l2Contracts[contractName] = getOEContract(
      contractName as keyof OEL2Contracts,
227
      l2ChainId,
228 229 230 231 232
      {
        address: opts.overrides?.l2?.[contractName] || contractAddress,
        signerOrProvider: opts.l2SignerOrProvider,
      }
    )
233 234 235 236 237 238 239
  }

  return {
    l1: l1Contracts,
    l2: l2Contracts,
  }
}
240 241

/**
242
 * Gets a series of bridge adapters for the given L2 chain ID.
243
 *
244
 * @param l2ChainId Chain ID for the L2 network.
245
 * @param messenger Cross chain messenger to connect to the bridge adapters
246
 * @param opts Additional options for connecting to the custom bridges.
247 248
 * @param opts.overrides Custom bridge adapters.
 * @returns An object containing all bridge adapters
249
 */
250
export const getBridgeAdapters = (
251
  l2ChainId: number,
252
  messenger: CrossChainMessenger,
253 254
  opts?: {
    overrides?: BridgeAdapterData
255
    contracts?: DeepPartial<OEContractsLike>
256
  }
257
): BridgeAdapters => {
258
  const adapterData: BridgeAdapterData = {
259
    ...(CONTRACT_ADDRESSES[l2ChainId] || opts?.contracts?.l1?.L1StandardBridge
260 261 262
      ? {
          Standard: {
            Adapter: StandardBridgeAdapter,
263
            l1Bridge:
264
              opts?.contracts?.l1?.L1StandardBridge ||
265
              CONTRACT_ADDRESSES[l2ChainId].l1.L1StandardBridge,
266 267 268 269
            l2Bridge: predeploys.L2StandardBridge,
          },
          ETH: {
            Adapter: ETHBridgeAdapter,
270
            l1Bridge:
271
              opts?.contracts?.l1?.L1StandardBridge ||
272
              CONTRACT_ADDRESSES[l2ChainId].l1.L1StandardBridge,
273 274 275 276 277
            l2Bridge: predeploys.L2StandardBridge,
          },
        }
      : {}),
    ...(BRIDGE_ADAPTER_DATA[l2ChainId] || {}),
278
    ...(opts?.overrides || {}),
279 280 281 282
  }

  const adapters: BridgeAdapters = {}
  for (const [bridgeName, bridgeData] of Object.entries(adapterData)) {
283
    adapters[bridgeName] = new bridgeData.Adapter({
284
      messenger,
285 286 287
      l1Bridge: bridgeData.l1Bridge,
      l2Bridge: bridgeData.l2Bridge,
    })
288 289
  }

290
  return adapters
291
}