@tokamak-network/thanos-sdk
Version:
Tools for working with Thanos
381 lines (364 loc) • 14.2 kB
text/typescript
import { ethers, Contract } from 'ethers'
import l1StandardBridge from '@tokamak-network/thanos-contracts/forge-artifacts/L1StandardBridge.sol/L1StandardBridge.json'
import l2StandardBridge from '@tokamak-network/thanos-contracts/forge-artifacts/L2StandardBridge.sol/L2StandardBridge.json'
import optimismMintableERC20 from '@tokamak-network/thanos-contracts/forge-artifacts/OptimismMintableERC20.sol/OptimismMintableERC20.json'
import optimismPortal from '@tokamak-network/thanos-contracts/forge-artifacts/OptimismPortal.sol/OptimismPortal.json'
import l1CrossDomainMessenger from '@tokamak-network/thanos-contracts/forge-artifacts/L1CrossDomainMessenger.sol/L1CrossDomainMessenger.json'
import l2CrossDomainMessenger from '@tokamak-network/thanos-contracts/forge-artifacts/L2CrossDomainMessenger.sol/L2CrossDomainMessenger.json'
import optimismMintableERC20Factory from '@tokamak-network/thanos-contracts/forge-artifacts/OptimismMintableERC20Factory.sol/OptimismMintableERC20Factory.json'
import proxyAdmin from '@tokamak-network/thanos-contracts/forge-artifacts/ProxyAdmin.sol/ProxyAdmin.json'
import l2OutputOracle from '@tokamak-network/thanos-contracts/forge-artifacts/L2OutputOracle.sol/L2OutputOracle.json'
import l1ERC721Bridge from '@tokamak-network/thanos-contracts/forge-artifacts/L1ERC721Bridge.sol/L1ERC721Bridge.json'
import l2ERC721Bridge from '@tokamak-network/thanos-contracts/forge-artifacts/L2ERC721Bridge.sol/L2ERC721Bridge.json'
import l1Block from '@tokamak-network/thanos-contracts/forge-artifacts/L1Block.sol/L1Block.json'
import l2ToL1MessagePasser from '@tokamak-network/thanos-contracts/forge-artifacts/L2ToL1MessagePasser.sol/L2ToL1MessagePasser.json'
import gasPriceOracle from '@tokamak-network/thanos-contracts/forge-artifacts/GasPriceOracle.sol/GasPriceOracle.json'
import { getContractInterface } from '@eth-optimism/contracts'
import { predeploys } from '@tokamak-network/core-utils'
import disputeGameFactory from '@tokamak-network/thanos-contracts/forge-artifacts/DisputeGameFactory.sol/DisputeGameFactory.json'
import optimismPortal2 from '@tokamak-network/thanos-contracts/forge-artifacts/OptimismPortal2.sol/OptimismPortal2.json'
import faultDisputeGame from '@tokamak-network/thanos-contracts/forge-artifacts/FaultDisputeGame.sol/FaultDisputeGame.json'
import l1UsdcBridge from '@tokamak-network/thanos-contracts/forge-artifacts/L1UsdcBridge.sol/L1UsdcBridge.json'
import l2UsdcBridge from '@tokamak-network/thanos-contracts/forge-artifacts/L2UsdcBridge.sol/L2UsdcBridge.json'
import { toAddress } from './coercion'
import { DeepPartial } from './type-utils'
import { CrossChainMessenger } from '../cross-chain-messenger'
import {
StandardBridgeAdapter,
ETHBridgeAdapter,
NativeTokenBridgeAdapter,
USDCBridgeAdapter,
} from '../adapters'
import {
CONTRACT_ADDRESSES,
DEFAULT_L2_CONTRACT_ADDRESSES,
BRIDGE_ADAPTER_DATA,
IGNORABLE_CONTRACTS,
} from './chain-constants'
import {
OEContracts,
OEL1Contracts,
OEL2Contracts,
OEContractsLike,
AddressLike,
BridgeAdapters,
BridgeAdapterData,
} 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 = {
AddressManager: 'Lib_AddressManager' as const,
OVM_L1BlockNumber: 'iOVM_L1BlockNumber' as const,
WETH: 'WETH9' as const,
BedrockMessagePasser: 'L2ToL1MessagePasser' as const,
}
export const getContractInterfaceBedrock = (
name: string
): ethers.utils.Interface => {
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
case 'DisputeGameFactory':
artifact = disputeGameFactory
break
case 'OptimismPortal2':
artifact = optimismPortal2
break
case 'FaultDisputeGame':
artifact = faultDisputeGame
break
case 'L1UsdcBridge':
artifact = l1UsdcBridge
break
case 'L2UsdcBridge':
artifact = l2UsdcBridge
break
}
return new ethers.utils.Interface(artifact.abi)
}
/**
* Returns an ethers.Contract object for the given name, connected to the appropriate address for
* the given L2 chain ID. Users can also provide a custom address to connect the contract to
* 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.
* @param l2ChainId Chain ID for the L2 network.
* @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,
l2ChainId: number,
opts: {
address?: AddressLike
signerOrProvider?: ethers.Signer | ethers.providers.Provider
} = {}
): Contract => {
// 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.
const addresses = CONTRACT_ADDRESSES[l2ChainId]
if (addresses === undefined && opts.address === undefined) {
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`
)
}
}
// 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)
}
return new Contract(
toAddress(
opts.address || addresses.l1[contractName] || addresses.l2[contractName]
),
iface,
opts.signerOrProvider
)
}
/**
* Automatically connects to all contract addresses, both L1 and L2, for the given L2 chain ID. The
* 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.
*
* @param l2ChainId Chain ID for the L2 network.
* @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 = (
l2ChainId: number,
opts: {
l1SignerOrProvider?: ethers.Signer | ethers.providers.Provider
l2SignerOrProvider?: ethers.Signer | ethers.providers.Provider
overrides?: DeepPartial<OEContractsLike>
} = {}
): OEContracts => {
const addresses = CONTRACT_ADDRESSES[l2ChainId] || {
l1: {
AddressManager: undefined,
L1CrossDomainMessenger: undefined,
L1StandardBridge: undefined,
StateCommitmentChain: undefined,
CanonicalTransactionChain: undefined,
BondManager: undefined,
OptimismPortal: undefined,
L2OutputOracle: undefined,
DisputeGameFactory: undefined,
OptimismPortal2: undefined,
},
l2: DEFAULT_L2_CONTRACT_ADDRESSES,
}
// Attach all L1 contracts.
const l1Contracts = {} as OEL1Contracts
for (const [contractName, contractAddress] of Object.entries(addresses.l1)) {
l1Contracts[contractName] = getOEContract(
contractName as keyof OEL1Contracts,
l2ChainId,
{
address: opts.overrides?.l1?.[contractName] || contractAddress,
signerOrProvider: opts.l1SignerOrProvider,
}
)
}
// Attach all L2 contracts.
const l2Contracts = {} as OEL2Contracts
for (const [contractName, contractAddress] of Object.entries(addresses.l2)) {
l2Contracts[contractName] = getOEContract(
contractName as keyof OEL2Contracts,
l2ChainId,
{
address: opts.overrides?.l2?.[contractName] || contractAddress,
signerOrProvider: opts.l2SignerOrProvider,
}
)
}
return {
l1: l1Contracts,
l2: l2Contracts,
}
}
/**
* Automatically connects to all contract addresses, both L1 and L2, for the given L2 chain ID. The
* 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.
*
* @param l2ChainId Chain ID for the L2 network.
* @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 getPortalsContracts = (
l2ChainId: number,
opts: {
l1SignerOrProvider?: ethers.Signer | ethers.providers.Provider
l2SignerOrProvider?: ethers.Signer | ethers.providers.Provider
overrides?: DeepPartial<OEContractsLike>
} = {}
): OEContracts => {
const addresses = CONTRACT_ADDRESSES[l2ChainId] || {
l1: {
OptimismPortal: undefined,
L2OutputOracle: undefined,
},
l2: DEFAULT_L2_CONTRACT_ADDRESSES,
}
// Attach all L1 contracts.
const l1Contracts = {} as OEL1Contracts
for (const [contractName, contractAddress] of Object.entries(addresses.l1)) {
l1Contracts[contractName] = getOEContract(
contractName as keyof OEL1Contracts,
l2ChainId,
{
address: opts.overrides?.l1?.[contractName] || contractAddress,
signerOrProvider: opts.l1SignerOrProvider,
}
)
}
// Attach all L2 contracts.
const l2Contracts = {} as OEL2Contracts
for (const [contractName, contractAddress] of Object.entries(addresses.l2)) {
l2Contracts[contractName] = getOEContract(
contractName as keyof OEL2Contracts,
l2ChainId,
{
address: opts.overrides?.l2?.[contractName] || contractAddress,
signerOrProvider: opts.l2SignerOrProvider,
}
)
}
return {
l1: l1Contracts,
l2: l2Contracts,
}
}
/**
* Gets a series of bridge adapters for the given L2 chain ID.
*
* @param l2ChainId Chain ID for the L2 network.
* @param messenger Cross chain messenger to connect to the bridge adapters
* @param opts Additional options for connecting to the custom bridges.
* @param opts.overrides Custom bridge adapters.
* @returns An object containing all bridge adapters
*/
export const getBridgeAdapters = (
l2ChainId: number,
messenger: CrossChainMessenger,
opts?: {
overrides?: BridgeAdapterData
contracts?: DeepPartial<OEContractsLike>
}
): BridgeAdapters => {
const adapterData: BridgeAdapterData = {
...(CONTRACT_ADDRESSES[l2ChainId] || opts?.contracts?.l1?.L1StandardBridge
? {
Standard: {
Adapter: StandardBridgeAdapter,
l1Bridge:
opts?.contracts?.l1?.L1StandardBridge ||
CONTRACT_ADDRESSES[l2ChainId].l1.L1StandardBridge,
l2Bridge: predeploys.L2StandardBridge,
},
ETH: {
Adapter: ETHBridgeAdapter,
l1Bridge:
opts?.contracts?.l1?.L1StandardBridge ||
CONTRACT_ADDRESSES[l2ChainId].l1.L1StandardBridge,
l2Bridge: predeploys.L2StandardBridge,
},
NativeToken: {
Adapter: NativeTokenBridgeAdapter,
l1Bridge:
opts?.contracts?.l1?.L1StandardBridge ||
CONTRACT_ADDRESSES[l2ChainId].l1.L1StandardBridge,
l2Bridge: predeploys.L2StandardBridge,
},
Usdc: {
Adapter: USDCBridgeAdapter,
l1Bridge:
opts?.contracts?.l1?.L1UsdcBridge ||
CONTRACT_ADDRESSES[l2ChainId].l1.L1UsdcBridge,
l2Bridge: predeploys.L2UsdcBridge,
},
}
: {}),
...(BRIDGE_ADAPTER_DATA[l2ChainId] || {}),
...(opts?.overrides || {}),
}
const adapters: BridgeAdapters = {}
for (const [bridgeName, bridgeData] of Object.entries(adapterData)) {
adapters[bridgeName] = new bridgeData.Adapter({
messenger,
l1Bridge: bridgeData.l1Bridge,
l2Bridge: bridgeData.l2Bridge,
})
}
return adapters
}