contracts.ts 6.69 KB
Newer Older
1
import { getContractInterface, predeploys } from '@eth-optimism/contracts'
2
import { getContractInterface as getContractInterfaceBedrock } from '@eth-optimism/contracts-bedrock'
3
import { ethers, Contract } from 'ethers'
4 5 6

import { toAddress } from './coercion'
import { DeepPartial } from './type-utils'
7
import { CrossChainMessenger } from '../cross-chain-messenger'
8 9 10 11 12 13
import { StandardBridgeAdapter, ETHBridgeAdapter } from '../adapters'
import {
  CONTRACT_ADDRESSES,
  DEFAULT_L2_CONTRACT_ADDRESSES,
  BRIDGE_ADAPTER_DATA,
} from './chain-constants'
14 15 16 17 18 19
import {
  OEContracts,
  OEL1Contracts,
  OEL2Contracts,
  OEContractsLike,
  AddressLike,
20 21
  BridgeAdapters,
  BridgeAdapterData,
22 23 24 25 26 27 28
} 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 = {
29 30 31
  AddressManager: 'Lib_AddressManager' as const,
  OVM_L1BlockNumber: 'iOVM_L1BlockNumber' as const,
  WETH: 'WETH9' as const,
32
  BedrockMessagePasser: 'L2ToL1MessagePasser' as const,
33 34 35 36
}

/**
 * Returns an ethers.Contract object for the given name, connected to the appropriate address for
37
 * the given L2 chain ID. Users can also provide a custom address to connect the contract to
38 39 40 41
 * 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.
42
 * @param l2ChainId Chain ID for the L2 network.
43 44 45 46 47 48 49
 * @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,
50
  l2ChainId: number,
51 52 53 54 55
  opts: {
    address?: AddressLike
    signerOrProvider?: ethers.Signer | ethers.providers.Provider
  } = {}
): Contract => {
56
  const addresses = CONTRACT_ADDRESSES[l2ChainId]
57 58
  if (addresses === undefined && opts.address === undefined) {
    throw new Error(
59
      `cannot get contract ${contractName} for unknown L2 chain ID ${l2ChainId}, you must provide an address`
60 61 62
    )
  }

63 64 65 66 67 68 69 70 71 72
  // 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)
  }

73 74 75 76
  return new Contract(
    toAddress(
      opts.address || addresses.l1[contractName] || addresses.l2[contractName]
    ),
77
    iface,
78 79 80 81 82
    opts.signerOrProvider
  )
}

/**
83
 * Automatically connects to all contract addresses, both L1 and L2, for the given L2 chain ID. The
84 85 86 87
 * 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.
 *
88
 * @param l2ChainId Chain ID for the L2 network.
89 90 91 92 93 94 95 96
 * @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 = (
97
  l2ChainId: number,
98 99 100 101 102 103
  opts: {
    l1SignerOrProvider?: ethers.Signer | ethers.providers.Provider
    l2SignerOrProvider?: ethers.Signer | ethers.providers.Provider
    overrides?: DeepPartial<OEContractsLike>
  } = {}
): OEContracts => {
104
  const addresses = CONTRACT_ADDRESSES[l2ChainId] || {
105 106 107 108 109 110 111
    l1: {
      AddressManager: undefined,
      L1CrossDomainMessenger: undefined,
      L1StandardBridge: undefined,
      StateCommitmentChain: undefined,
      CanonicalTransactionChain: undefined,
      BondManager: undefined,
112 113
      OptimismPortal: undefined,
      L2OutputOracle: undefined,
114 115 116 117 118
    },
    l2: DEFAULT_L2_CONTRACT_ADDRESSES,
  }

  // Attach all L1 contracts.
119
  const l1Contracts = {} as OEL1Contracts
120
  for (const [contractName, contractAddress] of Object.entries(addresses.l1)) {
121 122
    l1Contracts[contractName] = getOEContract(
      contractName as keyof OEL1Contracts,
123
      l2ChainId,
124 125 126 127 128
      {
        address: opts.overrides?.l1?.[contractName] || contractAddress,
        signerOrProvider: opts.l1SignerOrProvider,
      }
    )
129 130 131
  }

  // Attach all L2 contracts.
132
  const l2Contracts = {} as OEL2Contracts
133
  for (const [contractName, contractAddress] of Object.entries(addresses.l2)) {
134 135
    l2Contracts[contractName] = getOEContract(
      contractName as keyof OEL2Contracts,
136
      l2ChainId,
137 138 139 140 141
      {
        address: opts.overrides?.l2?.[contractName] || contractAddress,
        signerOrProvider: opts.l2SignerOrProvider,
      }
    )
142 143 144 145 146 147 148
  }

  return {
    l1: l1Contracts,
    l2: l2Contracts,
  }
}
149 150

/**
151
 * Gets a series of bridge adapters for the given L2 chain ID.
152
 *
153
 * @param l2ChainId Chain ID for the L2 network.
154
 * @param messenger Cross chain messenger to connect to the bridge adapters
155
 * @param opts Additional options for connecting to the custom bridges.
156 157
 * @param opts.overrides Custom bridge adapters.
 * @returns An object containing all bridge adapters
158
 */
159
export const getBridgeAdapters = (
160
  l2ChainId: number,
161
  messenger: CrossChainMessenger,
162 163
  opts?: {
    overrides?: BridgeAdapterData
164
    contracts?: DeepPartial<OEContractsLike>
165
  }
166
): BridgeAdapters => {
167
  const adapterData: BridgeAdapterData = {
168
    ...(CONTRACT_ADDRESSES[l2ChainId] || opts?.contracts?.l1?.L1StandardBridge
169 170 171
      ? {
          Standard: {
            Adapter: StandardBridgeAdapter,
172
            l1Bridge:
173
              opts?.contracts?.l1?.L1StandardBridge ||
174
              CONTRACT_ADDRESSES[l2ChainId].l1.L1StandardBridge,
175 176 177 178
            l2Bridge: predeploys.L2StandardBridge,
          },
          ETH: {
            Adapter: ETHBridgeAdapter,
179
            l1Bridge:
180
              opts?.contracts?.l1?.L1StandardBridge ||
181
              CONTRACT_ADDRESSES[l2ChainId].l1.L1StandardBridge,
182 183 184 185 186
            l2Bridge: predeploys.L2StandardBridge,
          },
        }
      : {}),
    ...(BRIDGE_ADAPTER_DATA[l2ChainId] || {}),
187
    ...(opts?.overrides || {}),
188 189 190 191
  }

  const adapters: BridgeAdapters = {}
  for (const [bridgeName, bridgeData] of Object.entries(adapterData)) {
192
    adapters[bridgeName] = new bridgeData.Adapter({
193
      messenger,
194 195 196
      l1Bridge: bridgeData.l1Bridge,
      l2Bridge: bridgeData.l2Bridge,
    })
197 198
  }

199
  return adapters
200
}