From fda55f040995c6483aa62ea97b249f4123cc0050 Mon Sep 17 00:00:00 2001 From: TucksonDev Date: Tue, 30 Dec 2025 15:01:08 +0000 Subject: [PATCH 1/4] Add script for fetching TokenBridge information from chain --- scripts/getTokenBridgeContractsInfo.ts | 340 +++++++++++++++++++++++++ 1 file changed, 340 insertions(+) create mode 100644 scripts/getTokenBridgeContractsInfo.ts diff --git a/scripts/getTokenBridgeContractsInfo.ts b/scripts/getTokenBridgeContractsInfo.ts new file mode 100644 index 000000000..195252efa --- /dev/null +++ b/scripts/getTokenBridgeContractsInfo.ts @@ -0,0 +1,340 @@ +import { ethers } from 'hardhat' +import '@nomiclabs/hardhat-ethers' +import { promises as fs } from 'fs' +import { L1AtomicTokenBridgeCreator__factory } from '../build/types' +import { execSync } from 'child_process' + +// Types +type Alloc = { + [key: string]: { + code: `0x${string}` + nonce: number + balance: string + storage?: { [key: `0x${string}`]: `0x${string}` } + } +} + +// Check environment variables +const parentChainRpc = process.env.PARENT_CHAIN_RPC as string +if (!parentChainRpc) { + throw new Error('PARENT_CHAIN_RPC not set') +} +const parentChainProvider = new ethers.providers.JsonRpcProvider(parentChainRpc) + +const tokenBridgeCreatorAddress = process.env + .TOKENBRIDGE_CREATOR_ADDRESS as `0x${string}` +if (!tokenBridgeCreatorAddress) { + throw new Error('TOKENBRIDGE_CREATOR_ADDRESS not set') +} + +// Helper function to get contract code and nonce +async function getAccountInformation( + address: `0x${string}`, + contractPath?: string, + excludeStorageEntries?: string[] +) { + const code = await parentChainProvider.getCode(address) + const nonce = await parentChainProvider.getTransactionCount(address) + const balance = await parentChainProvider.getBalance(address) + let storage = {} + if (contractPath) { + storage = await getStorageLayout( + address, + contractPath, + excludeStorageEntries + ) + } + return { + code: code as `0x${string}`, + nonce, + balance: balance.toString(), + storage, + } +} + +// Helper function and types to get the storage layout +type ForgeStorageEntry = { + astId: number + contract: string + label: string + offset: number + slot: string // note: string, we’ll convert to bigint + type: string + bytes: string +} + +type ForgeTypeInfo = { + label: string + encoding: 'inplace' | 'mapping' | 'dynamic_array' | string + numberOfBytes: string + base?: string + key?: string + value?: string + members?: any[] +} + +type ForgeStorageLayout = { + storage: ForgeStorageEntry[] + types: Record +} + +function isMapping(type: string): boolean { + return type.startsWith('t_mapping') +} + +function isArrayType(type: string): boolean { + return type.startsWith('t_array') +} + +// crude: fixed vs dynamic array from type name +function parseArrayInfo(type: string): { baseType: string; length?: number } { + // examples: + // t_array(t_address)dyn_storage + // t_array(t_uint256)40_storage + const m = type.match(/^t_array\(([^)]+)\)([^_]*)_storage$/) + if (!m) return { baseType: type } + + const baseType = m[1] + const lenPart = m[2] // "dyn" or "40" + if (lenPart === 'dyn') { + return { baseType } + } + + const length = Number(lenPart) + return { baseType, length } +} + +// pad slot -> 32-byte for keccak +function slotToPaddedHex(slot: bigint): string { + return ethers.utils.hexZeroPad(ethers.utils.hexlify(slot), 32) +} + +async function getStorageLayout( + address: `0x${string}`, + contractPath: string, + excludeStorageEntries?: string[] +) { + // Get Storage layout from contract code + const rawStorageLayout = execSync( + `forge inspect ${contractPath} storage --json`, + { encoding: 'utf8' } // so we get a string instead of Buffer + ) + const layout: ForgeStorageLayout = JSON.parse(rawStorageLayout) + const entries = layout.storage + + // Read storage entries in contract + const storage: Record = {} + const simpleSlots = new Set() + + for (const entry of entries) { + const slot = BigInt(entry.slot) + const type = entry.type + if (excludeStorageEntries && excludeStorageEntries.includes(entry.label)) { + continue + } + + // Mappings + if (isMapping(type)) { + // skip mappings: need explicit keys + continue + } + + // Arrays + if (isArrayType(type)) { + const { length } = parseArrayInfo(type) + + if (length !== undefined) { + // fixed-length array: contiguous slots + for (let i = 0; i < length; i++) { + simpleSlots.add(slot + BigInt(i)) + } + } else { + // dynamic array: slot holds length; data at keccak(slot) + i + const lenWord = await parentChainProvider.getStorageAt(address, slot) + storage[ethers.utils.hexlify(slot)] = lenWord + + const arrLength = Number(BigInt(lenWord)) + const dataStart = ethers.utils.keccak256(slotToPaddedHex(slot)) + const base = BigInt(dataStart) + + for (let i = 0; i < arrLength; i++) { + const elemSlot = base + BigInt(i) + const elemValue = await parentChainProvider.getStorageAt( + address, + elemSlot + ) + storage[ethers.utils.hexlify(elemSlot)] = elemValue + } + } + + continue + } + + // For all other types, read number of slots based on size + // This covers: + // - scalar vars (bytes <= 32 → 1 word) + // - structs (ex., bytes=192 → 6 words) + const typeInfo = layout.types[entry.type] + const bytes = Number(typeInfo.numberOfBytes) + const nWords = Math.max(1, Math.ceil(bytes / 32)) + for (let i = 0; i < nWords; i++) { + simpleSlots.add(slot + BigInt(i)) + } + } + + // Parallel fetch of simple slots + await Promise.all( + Array.from(simpleSlots).map(async slot => { + const value = await parentChainProvider.getStorageAt(address, slot) + storage[ethers.utils.hexlify(slot)] = value + }) + ) + + return storage +} + +async function main() { + // Initialize alloc object + const alloc: Alloc = {} + + // Create2 proxy + const create2ProxyAddress = + '0x4e59b44847b379578588920cA78FbF26c0B4956C' as `0x${string}` + alloc[create2ProxyAddress] = await getAccountInformation(create2ProxyAddress) + + // TokenBridgeCreator + alloc[tokenBridgeCreatorAddress] = await getAccountInformation( + tokenBridgeCreatorAddress, + 'contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol:L1AtomicTokenBridgeCreator' + ) + + // RetryableSender + const tokenBridgeCreator = L1AtomicTokenBridgeCreator__factory.connect( + tokenBridgeCreatorAddress, + parentChainProvider + ) + const retryableSenderAddress = + (await tokenBridgeCreator.retryableSender()) as `0x${string}` + alloc[retryableSenderAddress] = await getAccountInformation( + retryableSenderAddress, + 'contracts/tokenbridge/ethereum/L1TokenBridgeRetryableSender.sol:L1TokenBridgeRetryableSender' + ) + + // L1 templates + const { + routerTemplate, + standardGatewayTemplate, + customGatewayTemplate, + wethGatewayTemplate, + feeTokenBasedRouterTemplate, + feeTokenBasedStandardGatewayTemplate, + feeTokenBasedCustomGatewayTemplate, + upgradeExecutor, + } = await tokenBridgeCreator.l1Templates() + + alloc[routerTemplate] = await getAccountInformation( + routerTemplate as `0x${string}`, + 'contracts/tokenbridge/ethereum/gateway/L1GatewayRouter.sol:L1GatewayRouter' + ) + alloc[standardGatewayTemplate] = await getAccountInformation( + standardGatewayTemplate as `0x${string}`, + 'contracts/tokenbridge/ethereum/gateway/L1ERC20Gateway.sol:L1ERC20Gateway' + ) + alloc[customGatewayTemplate] = await getAccountInformation( + customGatewayTemplate as `0x${string}`, + 'contracts/tokenbridge/ethereum/gateway/L1CustomGateway.sol:L1CustomGateway' + ) + alloc[wethGatewayTemplate] = await getAccountInformation( + wethGatewayTemplate as `0x${string}`, + 'contracts/tokenbridge/ethereum/gateway/L1WethGateway.sol:L1WethGateway' + ) + alloc[feeTokenBasedRouterTemplate] = await getAccountInformation( + feeTokenBasedRouterTemplate as `0x${string}`, + 'contracts/tokenbridge/ethereum/gateway/L1OrbitGatewayRouter.sol:L1OrbitGatewayRouter' + ) + alloc[feeTokenBasedStandardGatewayTemplate] = await getAccountInformation( + feeTokenBasedStandardGatewayTemplate as `0x${string}`, + 'contracts/tokenbridge/ethereum/gateway/L1OrbitERC20Gateway.sol:L1OrbitERC20Gateway' + ) + alloc[feeTokenBasedCustomGatewayTemplate] = await getAccountInformation( + feeTokenBasedCustomGatewayTemplate as `0x${string}`, + 'contracts/tokenbridge/ethereum/gateway/L1OrbitCustomGateway.sol:L1OrbitCustomGateway' + ) + alloc[upgradeExecutor] = await getAccountInformation( + upgradeExecutor as `0x${string}` + ) + + // L2 templates + const l2TokenBridgeFactoryTemplate = + (await tokenBridgeCreator.l2TokenBridgeFactoryTemplate()) as `0x${string}` + alloc[l2TokenBridgeFactoryTemplate] = await getAccountInformation( + l2TokenBridgeFactoryTemplate, + 'contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol:L2AtomicTokenBridgeFactory' + ) + const l2RouterTemplate = + (await tokenBridgeCreator.l2RouterTemplate()) as `0x${string}` + alloc[l2RouterTemplate] = await getAccountInformation( + l2RouterTemplate, + 'contracts/tokenbridge/arbitrum/gateway/L2GatewayRouter.sol:L2GatewayRouter' + ) + const l2StandardGatewayTemplate = + (await tokenBridgeCreator.l2StandardGatewayTemplate()) as `0x${string}` + alloc[l2StandardGatewayTemplate] = await getAccountInformation( + l2StandardGatewayTemplate, + 'contracts/tokenbridge/arbitrum/gateway/L2ERC20Gateway.sol:L2ERC20Gateway' + ) + const l2CustomGatewayTemplate = + (await tokenBridgeCreator.l2CustomGatewayTemplate()) as `0x${string}` + alloc[l2CustomGatewayTemplate] = await getAccountInformation( + l2CustomGatewayTemplate, + 'contracts/tokenbridge/arbitrum/gateway/L2CustomGateway.sol:L2CustomGateway' + ) + const l2WethGatewayTemplate = + (await tokenBridgeCreator.l2WethGatewayTemplate()) as `0x${string}` + alloc[l2WethGatewayTemplate] = await getAccountInformation( + l2WethGatewayTemplate, + 'contracts/tokenbridge/arbitrum/gateway/L2WethGateway.sol:L2WethGateway' + ) + const l2WethTemplate = + (await tokenBridgeCreator.l2WethTemplate()) as `0x${string}` + alloc[l2WethTemplate] = await getAccountInformation( + l2WethTemplate, + 'contracts/tokenbridge/libraries/aeWETH.sol:aeWETH' + ) + const l2MulticallTemplate = + (await tokenBridgeCreator.l2MulticallTemplate()) as `0x${string}` + alloc[l2MulticallTemplate] = await getAccountInformation( + l2MulticallTemplate, + 'contracts/rpc-utils/MulticallV2.sol:ArbMulticall2' + ) + + // L1 Weth + const l1WethAddress = (await tokenBridgeCreator.l1Weth()) as `0x${string}` + alloc[l1WethAddress] = await getAccountInformation( + l1WethAddress, + 'contracts/tokenbridge/test/TestWETH9.sol:TestWETH9' + ) + + // L1 Multicall + const l1MulticallAddress = + (await tokenBridgeCreator.l1Multicall()) as `0x${string}` + alloc[l1MulticallAddress] = await getAccountInformation( + l1MulticallAddress, + 'contracts/rpc-utils/MulticallV2.sol:Multicall2' + ) + + // Craft file + const allocPath = + process.env.LOCAL_DEPLOYMENT_ALLOC_PATH !== undefined + ? process.env.LOCAL_DEPLOYMENT_ALLOC_PATH + : 'local_deployment_alloc.json' + + await fs.writeFile(allocPath, JSON.stringify(alloc, null, 2), 'utf8') +} + +main() + .then(() => process.exit(0)) + .catch((error: Error) => { + console.error(error) + process.exit(1) + }) From 96cefdccfba4e24453e6039178ec3987eb8f89b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20FP=20Mar=C3=ADa=20Franco=20P=C3=A9rez?= Date: Tue, 30 Dec 2025 16:03:07 +0000 Subject: [PATCH 2/4] Use create2 to deploy WETH --- .../local-deployment/localDeploymentLib.ts | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/scripts/local-deployment/localDeploymentLib.ts b/scripts/local-deployment/localDeploymentLib.ts index f3642b0e6..d211cc676 100644 --- a/scripts/local-deployment/localDeploymentLib.ts +++ b/scripts/local-deployment/localDeploymentLib.ts @@ -6,6 +6,7 @@ import { RollupAdminLogic__factory } from '@arbitrum/sdk/dist/lib/abi/factories/ import { execSync } from 'child_process' import { createTokenBridge, + deployContract, deployL1TokenBridgeCreator, getEstimateForDeployingFactory, registerGateway, @@ -116,12 +117,13 @@ export const setupTokenBridgeInLocalEnv = async () => { let l1Weth = process.env['PARENT_WETH_OVERRIDE'] if (l1Weth === undefined || l1Weth === '') { - const l1WethContract = await new TestWETH9__factory(parentDeployer).deploy( - 'WETH', - 'WETH' + const l1WethContract = await deployContract( + TestWETH9__factory, + parentDeployer, + ['WETH', 'WETH'], + false, + true ) - await l1WethContract.deployed() - l1Weth = l1WethContract.address } @@ -273,9 +275,11 @@ export const getLocalNetwork = async ( let nativeToken: string | undefined = undefined try { - nativeToken = await IERC20Bridge__factory.connect(data.bridge, l1Provider).nativeToken() - } - catch {} + nativeToken = await IERC20Bridge__factory.connect( + data.bridge, + l1Provider + ).nativeToken() + } catch {} const l2Network: ArbitrumNetwork = { nativeToken, @@ -292,7 +296,7 @@ export const getLocalNetwork = async ( isCustom: true, name: 'OrbitLocal', retryableLifetimeSeconds: 7 * 24 * 60 * 60, - isTestnet: true + isTestnet: true, } return { l2Network, From b0a65defd999bb22d3f8a34cae653debf3fbeda0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20FP=20Mar=C3=ADa=20Franco=20P=C3=A9rez?= Date: Tue, 30 Dec 2025 16:03:45 +0000 Subject: [PATCH 3/4] Add log --- scripts/local-deployment/localDeploymentLib.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/local-deployment/localDeploymentLib.ts b/scripts/local-deployment/localDeploymentLib.ts index d211cc676..ce9c09445 100644 --- a/scripts/local-deployment/localDeploymentLib.ts +++ b/scripts/local-deployment/localDeploymentLib.ts @@ -125,6 +125,7 @@ export const setupTokenBridgeInLocalEnv = async () => { true ) l1Weth = l1WethContract.address + console.log('Deployed WETH at', l1Weth) } //// run retryable estimate for deploying L2 factory From e222d4cdaeefc87773e39a5fce980667a80b886b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20FP=20Mar=C3=ADa=20Franco=20P=C3=A9rez?= Date: Wed, 31 Dec 2025 11:27:38 +0000 Subject: [PATCH 4/4] Add proxy admin and implementation contracts --- scripts/getTokenBridgeContractsInfo.ts | 94 ++++++++++++++++--- .../local-deployment/localDeploymentLib.ts | 1 - 2 files changed, 81 insertions(+), 14 deletions(-) diff --git a/scripts/getTokenBridgeContractsInfo.ts b/scripts/getTokenBridgeContractsInfo.ts index 195252efa..94b3ca715 100644 --- a/scripts/getTokenBridgeContractsInfo.ts +++ b/scripts/getTokenBridgeContractsInfo.ts @@ -14,6 +14,17 @@ type Alloc = { } } +// Constants +// EIP-1967 admin slot +const adminSlot = + '0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103' +// EIP-1967 implementation slot +const implSlot = + '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + +// Initialize alloc object +const alloc: Alloc = {} + // Check environment variables const parentChainRpc = process.env.PARENT_CHAIN_RPC as string if (!parentChainRpc) { @@ -63,16 +74,6 @@ type ForgeStorageEntry = { bytes: string } -type ForgeTypeInfo = { - label: string - encoding: 'inplace' | 'mapping' | 'dynamic_array' | string - numberOfBytes: string - base?: string - key?: string - value?: string - members?: any[] -} - type ForgeStorageLayout = { storage: ForgeStorageEntry[] types: Record @@ -193,10 +194,32 @@ async function getStorageLayout( return storage } -async function main() { - // Initialize alloc object - const alloc: Alloc = {} +async function getAddressFromStorage( + address: `0x${string}`, + rawslot: `0x${string}` +): Promise<`0x${string}`> { + const slot = ethers.BigNumber.from(rawslot) + const addressRaw = await parentChainProvider.getStorageAt( + address, + slot.toBigInt() + ) + return ethers.utils.getAddress( + ethers.utils.hexDataSlice(addressRaw, 12) + ) as `0x${string}` +} +async function addAddressToStorage( + contractAddress: `0x${string}`, + slot: `0x${string}`, + addressToAdd: `0x${string}` +): Promise { + if (!alloc[contractAddress].storage) { + alloc[contractAddress].storage = {} + } + alloc[contractAddress].storage![slot] = addressToAdd +} + +async function main() { // Create2 proxy const create2ProxyAddress = '0x4e59b44847b379578588920cA78FbF26c0B4956C' as `0x${string}` @@ -208,6 +231,36 @@ async function main() { 'contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol:L1AtomicTokenBridgeCreator' ) + // TokenBridgeCreator ProxyAdmin + const tokenBridgeCreatorProxyAdminAddress = await getAddressFromStorage( + tokenBridgeCreatorAddress, + adminSlot + ) + alloc[tokenBridgeCreatorProxyAdminAddress] = await getAccountInformation( + tokenBridgeCreatorProxyAdminAddress, + 'ProxyAdmin' + ) + await addAddressToStorage( + tokenBridgeCreatorAddress, + adminSlot, + tokenBridgeCreatorProxyAdminAddress + ) + + // TokenBridgeCreator Logic + const tokenBridgeCreatorLogicAddress = await getAddressFromStorage( + tokenBridgeCreatorAddress, + implSlot + ) + alloc[tokenBridgeCreatorLogicAddress] = await getAccountInformation( + tokenBridgeCreatorLogicAddress, + 'contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol:L1AtomicTokenBridgeCreator' + ) + await addAddressToStorage( + tokenBridgeCreatorAddress, + implSlot, + tokenBridgeCreatorLogicAddress + ) + // RetryableSender const tokenBridgeCreator = L1AtomicTokenBridgeCreator__factory.connect( tokenBridgeCreatorAddress, @@ -220,6 +273,21 @@ async function main() { 'contracts/tokenbridge/ethereum/L1TokenBridgeRetryableSender.sol:L1TokenBridgeRetryableSender' ) + // RetryableSender Logic + const retryableSenderLogicAddress = await getAddressFromStorage( + retryableSenderAddress, + implSlot + ) + alloc[retryableSenderLogicAddress] = await getAccountInformation( + retryableSenderLogicAddress, + 'contracts/tokenbridge/ethereum/L1TokenBridgeRetryableSender.sol:L1TokenBridgeRetryableSender' + ) + await addAddressToStorage( + retryableSenderAddress, + implSlot, + retryableSenderLogicAddress + ) + // L1 templates const { routerTemplate, diff --git a/scripts/local-deployment/localDeploymentLib.ts b/scripts/local-deployment/localDeploymentLib.ts index ce9c09445..d211cc676 100644 --- a/scripts/local-deployment/localDeploymentLib.ts +++ b/scripts/local-deployment/localDeploymentLib.ts @@ -125,7 +125,6 @@ export const setupTokenBridgeInLocalEnv = async () => { true ) l1Weth = l1WethContract.address - console.log('Deployed WETH at', l1Weth) } //// run retryable estimate for deploying L2 factory