diff --git a/src/createTokenBridge.integration.test.ts b/src/createTokenBridge.integration.test.ts index 99e68507..83d870dc 100644 --- a/src/createTokenBridge.integration.test.ts +++ b/src/createTokenBridge.integration.test.ts @@ -436,4 +436,52 @@ describe('createTokenBridge', () => { checkTokenBridgeContracts(tokenBridgeContracts); checkWethGateways(tokenBridgeContracts, { customFeeToken: true }); }); + + it('should throw when createTokenBridge is called multiple times', async () => { + const testnodeInformation = getInformationFromTestnode(); + + const tokenBridgeCreator = await deployTokenBridgeCreator({ + publicClient: nitroTestnodeL1Client, + }); + + const cfg = { + rollupOwner: l2RollupOwner.address, + rollupAddress: testnodeInformation.rollup, + account: l2RollupOwner, + parentChainPublicClient: nitroTestnodeL1Client, + orbitChainPublicClient: nitroTestnodeL2Client, + tokenBridgeCreatorAddressOverride: tokenBridgeCreator, + gasOverrides: { + gasLimit: { + base: 6_000_000n, + }, + }, + retryableGasOverrides: { + maxGasForFactory: { + base: 20_000_000n, + }, + maxGasForContracts: { + base: 20_000_000n, + }, + maxSubmissionCostForFactory: { + base: 4_000_000_000_000n, + }, + maxSubmissionCostForContracts: { + base: 4_000_000_000_000n, + }, + }, + setWethGatewayGasOverrides: { + gasLimit: { + base: 100_000n, + }, + }, + }; + const { tokenBridgeContracts } = await createTokenBridge(cfg); + await expect(createTokenBridge(cfg)).rejects.toThrowError( + `Token bridge contracts for Rollup ${testnodeInformation.rollup} are already deployed`, + ); + + checkTokenBridgeContracts(tokenBridgeContracts); + checkWethGateways(tokenBridgeContracts, { customFeeToken: false }); + }); }); diff --git a/src/createTokenBridge.ts b/src/createTokenBridge.ts index 25cea9ed..0d53e4b0 100644 --- a/src/createTokenBridge.ts +++ b/src/createTokenBridge.ts @@ -34,6 +34,58 @@ import { isCustomFeeTokenAddress } from './utils/isCustomFeeTokenAddress'; import { WithTokenBridgeCreatorAddressOverride } from './types/createTokenBridgeTypes'; import { TransactionRequestGasOverrides } from './utils/gasOverrides'; import { getBlockExplorerUrl } from './utils/getBlockExplorerUrl'; +import { tokenBridgeCreatorABI } from './contracts/TokenBridgeCreator'; +import { getTokenBridgeCreatorAddress } from './utils'; +import { rollupABI } from './contracts/Rollup'; + +/** + * If token bridge was already deployed, `createTokenBridge` will fail when waiting for retryables. + * This function returns true if token bridge was deployed previously. + * + * @param {String} assertTokenBridgeDoesntExistParams.parentChainPublicClient - The parent chain Viem Public Client + * @param {String} assertTokenBridgeDoesntExistParams.orbitChainPublicClient - The orbit chain Viem Public Client + * @param {String=} assertTokenBridgeDoesntExistParams.tokenBridgeCreatorAddress - The TokenBridgeCreator address. + * Default to getTokenBridgeCreatorAddress(parentChainPublicClient) if not provided + * @param {String} assertTokenBridgeDoesntExistParams.rollupAddress - The address of the rollup on the parent chain + * + * @returns true if token bridge was already deployed + */ +export async function isTokenBridgeDeployed< + TParentChain extends Chain | undefined, + TOrbitChain extends Chain | undefined, +>({ + parentChainPublicClient, + orbitChainPublicClient, + tokenBridgeCreatorAddress, + rollupAddress, +}: { + parentChainPublicClient: PublicClient; + orbitChainPublicClient: PublicClient; + tokenBridgeCreatorAddress?: Address; + rollupAddress: Address; +}) { + const inbox = await parentChainPublicClient.readContract({ + address: rollupAddress, + abi: rollupABI, + functionName: 'inbox', + }); + + const [router] = await parentChainPublicClient.readContract({ + address: tokenBridgeCreatorAddress ?? getTokenBridgeCreatorAddress(parentChainPublicClient), + abi: tokenBridgeCreatorABI, + functionName: 'inboxToL2Deployment', + args: [inbox], + }); + + if (router) { + const code = await orbitChainPublicClient.getBytecode({ address: router }); + if (code) { + return true; + } + } + + return false; +} export type CreateTokenBridgeParams< TParentChain extends Chain | undefined, @@ -171,6 +223,17 @@ export async function createTokenBridge< }: CreateTokenBridgeParams): Promise< CreateTokenBridgeResults > { + const isTokenBridgeAlreadyDeployed = await isTokenBridgeDeployed({ + parentChainPublicClient, + orbitChainPublicClient, + tokenBridgeCreatorAddress: tokenBridgeCreatorAddressOverride, + rollupAddress, + }); + + if (isTokenBridgeAlreadyDeployed) { + throw new Error(`Token bridge contracts for Rollup ${rollupAddress} are already deployed`); + } + const isCustomFeeTokenBridge = isCustomFeeTokenAddress(nativeTokenAddress); if (isCustomFeeTokenBridge) { // set the custom fee token diff --git a/src/index.ts b/src/index.ts index 5cbd3c94..521dd755 100644 --- a/src/index.ts +++ b/src/index.ts @@ -80,6 +80,7 @@ import { CreateTokenBridgeParams, CreateTokenBridgeResults, createTokenBridge, + isTokenBridgeDeployed, } from './createTokenBridge'; import { createTokenBridgeEnoughCustomFeeTokenAllowance, @@ -210,6 +211,7 @@ export { prepareKeyset, utils, // + isTokenBridgeDeployed, CreateTokenBridgeParams, CreateTokenBridgeResults, createTokenBridge,