diff --git a/audit-ci.jsonc b/audit-ci.jsonc index 6925ec78..68a5b08a 100644 --- a/audit-ci.jsonc +++ b/audit-ci.jsonc @@ -65,6 +65,21 @@ // from: @arbitrum/token-bridge-contracts>@openzeppelin/contracts-upgradeable // from: @arbitrum/nitro-contracts>@openzeppelin/contracts // from: @arbitrum/token-bridge-contracts>@openzeppelin/contracts - "GHSA-9vx6-7xxf-x967" + "GHSA-9vx6-7xxf-x967", + // https://github.com/advisories/GHSA-64vr-g452-qvp3 + // Vite DOM Clobbering gadget found in vite bundled scripts that leads to XSS + // vite is not used in production + // from: vitest > vite + "GHSA-64vr-g452-qvp3", + // https://github.com/advisories/GHSA-9cwx-2883-4wfx + // server.fs.deny bypassed when using ?import&raw + // vite is not used in production + // from: vitest > vite + "GHSA-9cwx-2883-4wfx", + // https://github.com/advisories/GHSA-gcx4-mw62-g8wm + // DOM Clobbering Gadget found in rollup bundled scripts that leads to XSS + // vite is not used in production + // from: vitest > vite + "GHSA-gcx4-mw62-g8wm" ] } diff --git a/src/createRollupPrepareTransactionRequest.ts b/src/createRollupPrepareTransactionRequest.ts index fa790d05..8dc3adc2 100644 --- a/src/createRollupPrepareTransactionRequest.ts +++ b/src/createRollupPrepareTransactionRequest.ts @@ -32,6 +32,7 @@ export type CreateRollupPrepareTransactionRequestParams; gasOverrides?: TransactionRequestGasOverrides; }> @@ -40,6 +41,7 @@ export type CreateRollupPrepareTransactionRequestParams({ params, account, + value, publicClient, gasOverrides, rollupCreatorAddressOverride, @@ -81,7 +83,7 @@ export async function createRollupPrepareTransactionRequest { 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, diff --git a/src/package.json b/src/package.json index 014cd073..f8b7b611 100644 --- a/src/package.json +++ b/src/package.json @@ -1,7 +1,7 @@ { "name": "@arbitrum/orbit-sdk", "description": "TypeScript SDK for building Arbitrum Orbit chains", - "version": "0.19.0", + "version": "0.20.0-beta.1", "main": "./dist/index.js", "files": [ "./dist" diff --git a/src/utils/getArbOSVersion.unit.test.ts b/src/utils/getArbOSVersion.unit.test.ts index a90aacee..9ccbd324 100644 --- a/src/utils/getArbOSVersion.unit.test.ts +++ b/src/utils/getArbOSVersion.unit.test.ts @@ -10,7 +10,7 @@ it('returns the ArbOS version of Arbitrum One', async () => { transport: http(), }); - expect(await getArbOSVersion(arbitrumOneClient)).toBe(31); + expect(await getArbOSVersion(arbitrumOneClient)).toBe(32); }); it('throws if the chain is not an Arbitrum chain', async () => {