From d7176d07ca95ec9a0cf08f680212a9fbd0b078a3 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Fri, 10 Nov 2023 09:55:40 +0100 Subject: [PATCH 01/48] Remove `goerli` from action/script name because target chain can be any chain --- package.json | 4 ++-- .../createTokenBridge.ts | 8 ++++---- .../deployTokenBridgeCreator.ts | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) rename scripts/{goerli-deployment => deployment}/createTokenBridge.ts (96%) rename scripts/{goerli-deployment => deployment}/deployTokenBridgeCreator.ts (93%) diff --git a/package.json b/package.json index df01055d5f..48b5c78da6 100644 --- a/package.json +++ b/package.json @@ -21,8 +21,8 @@ "test:e2e:local-env": "yarn hardhat test test-e2e/*", "test:storage": "./scripts/storage_layout_test.bash", "deploy:local:token-bridge": "ts-node ./scripts/local-deployment/deployCreatorAndCreateTokenBridge.ts", - "deploy:goerli:token-bridge-creator": "ts-node ./scripts/goerli-deployment/deployTokenBridgeCreator.ts", - "create:goerli:token-bridge": "ts-node ./scripts/goerli-deployment/createTokenBridge.ts", + "deploy:token-bridge-creator": "ts-node ./scripts/deployment/deployTokenBridgeCreator.ts", + "create:token-bridge": "ts-node ./scripts/deployment/createTokenBridge.ts", "test:tokenbridge:deployment": "hardhat test test-e2e/tokenBridgeDeploymentTest.ts", "typechain": "hardhat typechain", "deploy:tokenbridge": "hardhat run scripts/deploy_token_bridge_l1.ts --network mainnet", diff --git a/scripts/goerli-deployment/createTokenBridge.ts b/scripts/deployment/createTokenBridge.ts similarity index 96% rename from scripts/goerli-deployment/createTokenBridge.ts rename to scripts/deployment/createTokenBridge.ts index b2a6039300..f99105e25e 100644 --- a/scripts/goerli-deployment/createTokenBridge.ts +++ b/scripts/deployment/createTokenBridge.ts @@ -31,7 +31,7 @@ export const envVars = { * @param l2Url * @returns */ -export const createTokenBridgeOnGoerli = async () => { +export const createTokenBridgeOnTargetChain = async () => { if (envVars.rollupAddress == undefined) throw new Error('Missing ROLLUP_ADDRESS in env vars') if (envVars.rollupOwner == undefined) @@ -51,7 +51,7 @@ export const createTokenBridgeOnGoerli = async () => { const l1Deployer = getSigner(l1Provider, envVars.baseChainDeployerKey) const l2Provider = new JsonRpcProvider(envVars.childChainRpc) - const { l1Network, l2Network: corel2Network } = await registerGoerliNetworks( + const { l1Network, l2Network: corel2Network } = await registerNetworks( l1Provider, l2Provider, envVars.rollupAddress @@ -98,7 +98,7 @@ export const createTokenBridgeOnGoerli = async () => { } } -const registerGoerliNetworks = async ( +const registerNetworks = async ( l1Provider: JsonRpcProvider, l2Provider: JsonRpcProvider, rollupAddress: string @@ -170,7 +170,7 @@ const registerGoerliNetworks = async ( } async function main() { - const { l1Network, l2Network } = await createTokenBridgeOnGoerli() + const { l1Network, l2Network } = await createTokenBridgeOnTargetChain() const NETWORK_FILE = 'network.json' fs.writeFileSync( NETWORK_FILE, diff --git a/scripts/goerli-deployment/deployTokenBridgeCreator.ts b/scripts/deployment/deployTokenBridgeCreator.ts similarity index 93% rename from scripts/goerli-deployment/deployTokenBridgeCreator.ts rename to scripts/deployment/deployTokenBridgeCreator.ts index c5a17a01e7..9f9810b193 100644 --- a/scripts/goerli-deployment/deployTokenBridgeCreator.ts +++ b/scripts/deployment/deployTokenBridgeCreator.ts @@ -15,7 +15,7 @@ export const envVars = { childChainRpc: process.env['ORBIT_RPC'] as string, } -const ARB_GOERLI_WETH = '0xEe01c0CD76354C383B8c7B4e65EA88D00B06f36f' +const BASECHAIN_WETH = '0xEe01c0CD76354C383B8c7B4e65EA88D00B06f36f' /** * Steps: @@ -42,16 +42,16 @@ export const deployTokenBridgeCreator = async (rollupAddress: string) => { const l1Deployer = getSigner(l1Provider, envVars.baseChainDeployerKey) const l2Provider = new JsonRpcProvider(envVars.childChainRpc) - await registerGoerliNetworks(l1Provider, l2Provider, rollupAddress) + await registerNetworks(l1Provider, l2Provider, rollupAddress) // deploy L1 creator and set templates const { l1TokenBridgeCreator, retryableSender } = - await deployL1TokenBridgeCreator(l1Deployer, l2Provider, ARB_GOERLI_WETH, true) + await deployL1TokenBridgeCreator(l1Deployer, l2Provider, BASECHAIN_WETH, true) return { l1TokenBridgeCreator, retryableSender } } -const registerGoerliNetworks = async ( +const registerNetworks = async ( l1Provider: JsonRpcProvider, l2Provider: JsonRpcProvider, rollupAddress: string From 8431e122a9a272e5cdcbf5aa67ea1bf3f7082fc0 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Fri, 10 Nov 2023 10:14:52 +0100 Subject: [PATCH 02/48] Make basechain WETH address an env var instead of having it hard-coded --- .env-sample | 19 +++++++++++-------- .../deployment/deployTokenBridgeCreator.ts | 13 +++++++------ 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/.env-sample b/.env-sample index 0fd2718b05..5b16893ccd 100644 --- a/.env-sample +++ b/.env-sample @@ -1,13 +1,16 @@ -## Rollup on top of which token bridge will be created -ROLLUP_ADDRESS="" -ROLLUP_OWNER="" -L1_TOKEN_BRIDGE_CREATOR="" -# needed for verification -L1_RETRYABLE_SENDER="" - ## RPC endpoints BASECHAIN_RPC="" ORBIT_RPC="" -## Deployer key used for deploying creator and creating token bridge +## Deployer key used for deploying creator or creating token bridge BASECHAIN_DEPLOYER_KEY="" + +## WETH address on the basechain. It will be set and used in the TokenBridgeCreator +BASECHAIN_WETH="" + +## Rollup on top of which token bridge will be created +ROLLUP_ADDRESS="" +ROLLUP_OWNER="" +L1_TOKEN_BRIDGE_CREATOR="" +# needed for verification +L1_RETRYABLE_SENDER="" \ No newline at end of file diff --git a/scripts/deployment/deployTokenBridgeCreator.ts b/scripts/deployment/deployTokenBridgeCreator.ts index 9f9810b193..d302eacf96 100644 --- a/scripts/deployment/deployTokenBridgeCreator.ts +++ b/scripts/deployment/deployTokenBridgeCreator.ts @@ -13,10 +13,9 @@ export const envVars = { baseChainRpc: process.env['BASECHAIN_RPC'] as string, baseChainDeployerKey: process.env['BASECHAIN_DEPLOYER_KEY'] as string, childChainRpc: process.env['ORBIT_RPC'] as string, + baseChainWeth: process.env['BASECHAIN_WETH'] as string, } -const BASECHAIN_WETH = '0xEe01c0CD76354C383B8c7B4e65EA88D00B06f36f' - /** * Steps: * - read network info from local container and register networks @@ -31,12 +30,14 @@ const BASECHAIN_WETH = '0xEe01c0CD76354C383B8c7B4e65EA88D00B06f36f' * @returns */ export const deployTokenBridgeCreator = async (rollupAddress: string) => { - if (envVars.baseChainRpc == undefined) + if (envVars.baseChainRpc == undefined || envVars.baseChainRpc == '') throw new Error('Missing BASECHAIN_RPC in env vars') - if (envVars.baseChainDeployerKey == undefined) + if (envVars.baseChainDeployerKey == undefined || envVars.baseChainDeployerKey == '') throw new Error('Missing BASECHAIN_DEPLOYER_KEY in env vars') - if (envVars.childChainRpc == undefined) + if (envVars.childChainRpc == undefined || envVars.childChainRpc == '') throw new Error('Missing ORBIT_RPC in env vars') + if (envVars.baseChainWeth == undefined || envVars.baseChainWeth == '') + throw new Error('Missing BASECHAIN_WETH in env vars') const l1Provider = new JsonRpcProvider(envVars.baseChainRpc) const l1Deployer = getSigner(l1Provider, envVars.baseChainDeployerKey) @@ -46,7 +47,7 @@ export const deployTokenBridgeCreator = async (rollupAddress: string) => { // deploy L1 creator and set templates const { l1TokenBridgeCreator, retryableSender } = - await deployL1TokenBridgeCreator(l1Deployer, l2Provider, BASECHAIN_WETH, true) + await deployL1TokenBridgeCreator(l1Deployer, l2Provider, envVars.baseChainWeth, true) return { l1TokenBridgeCreator, retryableSender } } From ed211529fcc5a243fd9f5e4cf39b9dd84a1657a5 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Fri, 10 Nov 2023 10:31:39 +0100 Subject: [PATCH 03/48] Read rollup address from envVars instead of hardcoding --- scripts/deployment/deployTokenBridgeCreator.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/deployment/deployTokenBridgeCreator.ts b/scripts/deployment/deployTokenBridgeCreator.ts index d302eacf96..ad697724d1 100644 --- a/scripts/deployment/deployTokenBridgeCreator.ts +++ b/scripts/deployment/deployTokenBridgeCreator.ts @@ -14,6 +14,7 @@ export const envVars = { baseChainDeployerKey: process.env['BASECHAIN_DEPLOYER_KEY'] as string, childChainRpc: process.env['ORBIT_RPC'] as string, baseChainWeth: process.env['BASECHAIN_WETH'] as string, + rollupAddress: process.env['ROLLUP_ADDRESS'] as string, } /** @@ -124,8 +125,8 @@ const registerNetworks = async ( } async function main() { - // this is just random Orbit rollup that will be used to estimate gas needed to deploy L2 token bridge factory via retryable - const rollupAddress = '0x8223bd899C6643483872ed2A7b105b2aC9C8aBEc' + // this is just any Orbit rollup that will be used to estimate gas needed to deploy L2 token bridge factory via retryable + const rollupAddress = envVars.rollupAddress const { l1TokenBridgeCreator, retryableSender } = await deployTokenBridgeCreator(rollupAddress) From e60159d4d2f51fc6c6b475cbe8f15ac7b1f12c11 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Fri, 10 Nov 2023 12:14:07 +0100 Subject: [PATCH 04/48] Add option to provide GAS_LIMIT_FOR_L2_FACTORY_DEPLOYMENT as env var --- .env-sample | 8 +++- scripts/atomicTokenBridgeDeployer.ts | 10 +---- .../deployment/deployTokenBridgeCreator.ts | 42 +++++++++++++++++-- .../deployCreatorAndCreateTokenBridge.ts | 15 ++++++- 4 files changed, 60 insertions(+), 15 deletions(-) diff --git a/.env-sample b/.env-sample index 5b16893ccd..ea550ee862 100644 --- a/.env-sample +++ b/.env-sample @@ -1,6 +1,5 @@ -## RPC endpoints +## RPC endpoint BASECHAIN_RPC="" -ORBIT_RPC="" ## Deployer key used for deploying creator or creating token bridge BASECHAIN_DEPLOYER_KEY="" @@ -8,8 +7,13 @@ BASECHAIN_DEPLOYER_KEY="" ## WETH address on the basechain. It will be set and used in the TokenBridgeCreator BASECHAIN_WETH="" +## Gas limit for deploying child chain factory needs to be provided to the TokenBridgeCreator when templates are set. +## If this param is not provided then gas limit will be estimated from child chain (specified by ORBIT_RPC and ROLLUP_ADDRESS) +GAS_LIMIT_FOR_L2_FACTORY_DEPLOYMENT=5138333 + ## Rollup on top of which token bridge will be created ROLLUP_ADDRESS="" +ORBIT_RPC="" ROLLUP_OWNER="" L1_TOKEN_BRIDGE_CREATOR="" # needed for verification diff --git a/scripts/atomicTokenBridgeDeployer.ts b/scripts/atomicTokenBridgeDeployer.ts index 9026fa15e8..0c245107dc 100644 --- a/scripts/atomicTokenBridgeDeployer.ts +++ b/scripts/atomicTokenBridgeDeployer.ts @@ -243,8 +243,8 @@ export const createTokenBridge = async ( */ export const deployL1TokenBridgeCreator = async ( l1Deployer: Signer, - l2Provider: ethers.providers.Provider, l1WethAddress: string, + gasLimitForL2FactoryDeployment: BigNumber, verifyContracts: boolean = false ) => { /// deploy creator behind proxy @@ -383,12 +383,6 @@ export const deployL1TokenBridgeCreator = async ( const l1Multicall = await new Multicall2__factory(l1Deployer).deploy() await l1Multicall.deployed() - //// run retryable estimate for deploying L2 factory - const deployFactoryGasParams = await getEstimateForDeployingFactory( - l1Deployer, - l2Provider - ) - await ( await l1TokenBridgeCreator.setTemplates( l1Templates, @@ -400,7 +394,7 @@ export const deployL1TokenBridgeCreator = async ( l2WethAddressOnL1.address, l1WethAddress, l1Multicall.address, - deployFactoryGasParams.gasLimit + gasLimitForL2FactoryDeployment ) ).wait() diff --git a/scripts/deployment/deployTokenBridgeCreator.ts b/scripts/deployment/deployTokenBridgeCreator.ts index ad697724d1..1ffb81651a 100644 --- a/scripts/deployment/deployTokenBridgeCreator.ts +++ b/scripts/deployment/deployTokenBridgeCreator.ts @@ -3,9 +3,11 @@ import { L1Network, L2Network, addCustomNetwork } from '@arbitrum/sdk' import { RollupAdminLogic__factory } from '@arbitrum/sdk/dist/lib/abi/factories/RollupAdminLogic__factory' import { deployL1TokenBridgeCreator, + getEstimateForDeployingFactory, getSigner, } from '../atomicTokenBridgeDeployer' import dotenv from 'dotenv' +import { BigNumber } from 'ethers' dotenv.config() @@ -15,6 +17,8 @@ export const envVars = { childChainRpc: process.env['ORBIT_RPC'] as string, baseChainWeth: process.env['BASECHAIN_WETH'] as string, rollupAddress: process.env['ROLLUP_ADDRESS'] as string, + gasLimitForL2FactoryDeployment: + process.env['GAS_LIMIT_FOR_L2_FACTORY_DEPLOYMENT'], } /** @@ -33,22 +37,52 @@ export const envVars = { export const deployTokenBridgeCreator = async (rollupAddress: string) => { if (envVars.baseChainRpc == undefined || envVars.baseChainRpc == '') throw new Error('Missing BASECHAIN_RPC in env vars') - if (envVars.baseChainDeployerKey == undefined || envVars.baseChainDeployerKey == '') + if ( + envVars.baseChainDeployerKey == undefined || + envVars.baseChainDeployerKey == '' + ) throw new Error('Missing BASECHAIN_DEPLOYER_KEY in env vars') if (envVars.childChainRpc == undefined || envVars.childChainRpc == '') throw new Error('Missing ORBIT_RPC in env vars') if (envVars.baseChainWeth == undefined || envVars.baseChainWeth == '') throw new Error('Missing BASECHAIN_WETH in env vars') + if ( + (envVars.rollupAddress == undefined || envVars.rollupAddress == '') && + (envVars.childChainRpc == undefined || envVars.childChainRpc == '') && + envVars.gasLimitForL2FactoryDeployment == undefined + ) + throw new Error( + 'Either GAS_LIMIT_FOR_L2_FACTORY_DEPLOYMENT or (ROLLUP_ADDRESS and ORBIT_RPC) must be set in env vars' + ) const l1Provider = new JsonRpcProvider(envVars.baseChainRpc) const l1Deployer = getSigner(l1Provider, envVars.baseChainDeployerKey) - const l2Provider = new JsonRpcProvider(envVars.childChainRpc) - await registerNetworks(l1Provider, l2Provider, rollupAddress) + // get gas limit for L2 factory deployment from env var or do retryable estimate + let gasLimitForL2FactoryDeployment: BigNumber + if (envVars.gasLimitForL2FactoryDeployment) { + gasLimitForL2FactoryDeployment = BigNumber.from( + envVars.gasLimitForL2FactoryDeployment + ) + } else { + const l2Provider = new JsonRpcProvider(envVars.childChainRpc) + await registerNetworks(l1Provider, l2Provider, rollupAddress) + //// run retryable estimate for deploying L2 factory + const deployFactoryGasParams = await getEstimateForDeployingFactory( + l1Deployer, + l2Provider + ) + gasLimitForL2FactoryDeployment = deployFactoryGasParams.gasLimit + } // deploy L1 creator and set templates const { l1TokenBridgeCreator, retryableSender } = - await deployL1TokenBridgeCreator(l1Deployer, l2Provider, envVars.baseChainWeth, true) + await deployL1TokenBridgeCreator( + l1Deployer, + envVars.baseChainWeth, + gasLimitForL2FactoryDeployment, + true + ) return { l1TokenBridgeCreator, retryableSender } } diff --git a/scripts/local-deployment/deployCreatorAndCreateTokenBridge.ts b/scripts/local-deployment/deployCreatorAndCreateTokenBridge.ts index 1c18c1ed6e..af8435ce6c 100644 --- a/scripts/local-deployment/deployCreatorAndCreateTokenBridge.ts +++ b/scripts/local-deployment/deployCreatorAndCreateTokenBridge.ts @@ -8,6 +8,7 @@ import { execSync } from 'child_process' import { createTokenBridge, deployL1TokenBridgeCreator, + getEstimateForDeployingFactory, } from '../atomicTokenBridgeDeployer' import { l2Networks } from '@arbitrum/sdk/dist/lib/dataEntities/networks' @@ -80,8 +81,20 @@ export const setupTokenBridgeInLocalEnv = async () => { console.log('Deploying L1TokenBridgeCreator') // a random address for l1Weth const l1Weth = '0x05EcEffc7CBA4e43a410340E849052AD43815aCA' + + //// run retryable estimate for deploying L2 factory + const deployFactoryGasParams = await getEstimateForDeployingFactory( + l1Deployer, + l2Deployer.provider! + ) + const gasLimitForL2FactoryDeployment = deployFactoryGasParams.gasLimit + const { l1TokenBridgeCreator, retryableSender } = - await deployL1TokenBridgeCreator(l1Deployer, l2Deployer.provider!, l1Weth) + await deployL1TokenBridgeCreator( + l1Deployer, + l1Weth, + gasLimitForL2FactoryDeployment + ) console.log('L1TokenBridgeCreator', l1TokenBridgeCreator.address) console.log('L1TokenBridgeRetryableSender', retryableSender.address) From 7a8d26c7e670a4116ee0b4d021027a1c418053d6 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Fri, 10 Nov 2023 12:49:12 +0100 Subject: [PATCH 05/48] Clean up --- .env-sample | 3 ++ .../deployment/deployTokenBridgeCreator.ts | 32 +++++++++---------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/.env-sample b/.env-sample index ea550ee862..1318ffff18 100644 --- a/.env-sample +++ b/.env-sample @@ -11,6 +11,9 @@ BASECHAIN_WETH="" ## If this param is not provided then gas limit will be estimated from child chain (specified by ORBIT_RPC and ROLLUP_ADDRESS) GAS_LIMIT_FOR_L2_FACTORY_DEPLOYMENT=5138333 +## Contract verification +ARBISCAN_API_KEY="" + ## Rollup on top of which token bridge will be created ROLLUP_ADDRESS="" ORBIT_RPC="" diff --git a/scripts/deployment/deployTokenBridgeCreator.ts b/scripts/deployment/deployTokenBridgeCreator.ts index 1ffb81651a..900ea50e22 100644 --- a/scripts/deployment/deployTokenBridgeCreator.ts +++ b/scripts/deployment/deployTokenBridgeCreator.ts @@ -34,26 +34,25 @@ export const envVars = { * @param l2Url * @returns */ -export const deployTokenBridgeCreator = async (rollupAddress: string) => { - if (envVars.baseChainRpc == undefined || envVars.baseChainRpc == '') +export const deployTokenBridgeCreator = async () => { + if (!envVars.baseChainRpc || envVars.baseChainRpc == '') { throw new Error('Missing BASECHAIN_RPC in env vars') - if ( - envVars.baseChainDeployerKey == undefined || - envVars.baseChainDeployerKey == '' - ) + } + if (!envVars.baseChainDeployerKey || envVars.baseChainDeployerKey == '') { throw new Error('Missing BASECHAIN_DEPLOYER_KEY in env vars') - if (envVars.childChainRpc == undefined || envVars.childChainRpc == '') - throw new Error('Missing ORBIT_RPC in env vars') - if (envVars.baseChainWeth == undefined || envVars.baseChainWeth == '') + } + if (!envVars.baseChainWeth || envVars.baseChainWeth == '') { throw new Error('Missing BASECHAIN_WETH in env vars') + } if ( - (envVars.rollupAddress == undefined || envVars.rollupAddress == '') && - (envVars.childChainRpc == undefined || envVars.childChainRpc == '') && - envVars.gasLimitForL2FactoryDeployment == undefined - ) + (!envVars.rollupAddress || envVars.rollupAddress == '') && + (!envVars.childChainRpc || envVars.childChainRpc == '') && + !envVars.gasLimitForL2FactoryDeployment + ) { throw new Error( 'Either GAS_LIMIT_FOR_L2_FACTORY_DEPLOYMENT or (ROLLUP_ADDRESS and ORBIT_RPC) must be set in env vars' ) + } const l1Provider = new JsonRpcProvider(envVars.baseChainRpc) const l1Deployer = getSigner(l1Provider, envVars.baseChainDeployerKey) @@ -66,7 +65,7 @@ export const deployTokenBridgeCreator = async (rollupAddress: string) => { ) } else { const l2Provider = new JsonRpcProvider(envVars.childChainRpc) - await registerNetworks(l1Provider, l2Provider, rollupAddress) + await registerNetworks(l1Provider, l2Provider, envVars.rollupAddress) //// run retryable estimate for deploying L2 factory const deployFactoryGasParams = await getEstimateForDeployingFactory( l1Deployer, @@ -159,10 +158,9 @@ const registerNetworks = async ( } async function main() { - // this is just any Orbit rollup that will be used to estimate gas needed to deploy L2 token bridge factory via retryable - const rollupAddress = envVars.rollupAddress + console.log('Deploying token bridge creator...') const { l1TokenBridgeCreator, retryableSender } = - await deployTokenBridgeCreator(rollupAddress) + await deployTokenBridgeCreator() console.log('Token bridge creator deployed!') console.log('L1TokenBridgeCreator:', l1TokenBridgeCreator.address) From 00f8f13a8472b8963f248e86d1cf5234fa8e3f08 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Fri, 10 Nov 2023 13:06:02 +0100 Subject: [PATCH 06/48] Add instructions for deploying RollupCreator and TokenBridgeCreator --- docs/deployment.md | 63 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 docs/deployment.md diff --git a/docs/deployment.md b/docs/deployment.md new file mode 100644 index 0000000000..a56322f0ba --- /dev/null +++ b/docs/deployment.md @@ -0,0 +1,63 @@ +# How to deploy RollupCreator and TokenBridgeCreator? + +## RollupCreator +RollupCreator is in nitro-contracts repo +``` +cd nitro-contracts +``` + +Checkout target code, ie. +``` +git checkout v1.1.0 +``` + +Install dependencies and build +``` +yarn install +yarn build +``` + +Populate .env +``` +DEVNET_PRIVKEY or MAINNET_PRIVKEY +ARBISCAN_API_KEY +``` + +Finally deploy it, using `--network` flag to specify network. + +Ie. to deploy to Arbitrum Sepolia +``` +yarn run deploy-factory --network arbSepolia +``` + +To deploy to Arbitrum One +``` +yarn run deploy-factory --network arb1 +``` + + +## TokenBridgeCreator +Checkout target code, install dependencies and build +``` +cd token-bridge-contracts +yarn install +yarn build +``` + + +Populate .env +``` +BASECHAIN_RPC +BASECHAIN_DEPLOYER_KEY +BASECHAIN_WETH +GAS_LIMIT_FOR_L2_FACTORY_DEPLOYMENT +ARBISCAN_API_KEY +``` + +Note: Gas limit for deploying child chain factory via retryable needs to be provided to the TokenBridgeCreator when templates are set. This value can be obtained in 2 ways - 1st is to provide `ORBIT_RPC` and `ROLLUP_ADDRESS` env vars, and script will then use Arbitrum SDK to estimate gas needed for deploying L2 factory. Other way to do it is much simpler - provide hardcoded value by setting the `GAS_LIMIT_FOR_L2_FACTORY_DEPLOYMENT`. Previous deployments showed that gas needed is ~5140000. Adding a bit of buffer on top, we can set this value to `GAS_LIMIT_FOR_L2_FACTORY_DEPLOYMENT=6000000`. + + +Finally, deploy token bridge creator. Target chain is defined by `BASECHAIN_RPC` env var (no need to provide `--network` flag). +``` +yarn run deploy:token-bridge-creator +``` \ No newline at end of file From 8c7ffbd8e36b79de7f41e37d1b90fa4e7e823d23 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Fri, 10 Nov 2023 13:09:39 +0100 Subject: [PATCH 07/48] Add buffer --- .env-sample | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.env-sample b/.env-sample index 1318ffff18..4a215dc6c3 100644 --- a/.env-sample +++ b/.env-sample @@ -8,12 +8,13 @@ BASECHAIN_DEPLOYER_KEY="" BASECHAIN_WETH="" ## Gas limit for deploying child chain factory needs to be provided to the TokenBridgeCreator when templates are set. -## If this param is not provided then gas limit will be estimated from child chain (specified by ORBIT_RPC and ROLLUP_ADDRESS) -GAS_LIMIT_FOR_L2_FACTORY_DEPLOYMENT=5138333 +## If this param is not provided then gas limit will be estimated using SDK from child chain (specified by ORBIT_RPC and ROLLUP_ADDRESS) +GAS_LIMIT_FOR_L2_FACTORY_DEPLOYMENT=6000000 ## Contract verification ARBISCAN_API_KEY="" +### Vars for creating token bridge from existing TokenBridgeCreator ## Rollup on top of which token bridge will be created ROLLUP_ADDRESS="" ORBIT_RPC="" From bb71c067b07a40574a5c4b2aa2859ec2dbd2a992 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Fri, 10 Nov 2023 13:52:41 +0100 Subject: [PATCH 08/48] Store multicall addresses --- scripts/atomicTokenBridgeDeployer.ts | 7 +++++++ scripts/deployment/createTokenBridge.ts | 4 ++-- .../local-deployment/deployCreatorAndCreateTokenBridge.ts | 4 ++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/scripts/atomicTokenBridgeDeployer.ts b/scripts/atomicTokenBridgeDeployer.ts index 0c245107dc..3c0b96fdee 100644 --- a/scripts/atomicTokenBridgeDeployer.ts +++ b/scripts/atomicTokenBridgeDeployer.ts @@ -217,12 +217,18 @@ export const createTokenBridge = async ( const l2ProxyAdmin = await l1TokenBridgeCreator.getCanonicalL2ProxyAdminAddress(chainId) + const l1Multicall = await l1TokenBridgeCreator.l1Multicall() + const l2Multicall = await l1TokenBridgeCreator.getCanonicalL2Multicall( + chainId + ) + return { l1Router, l1StandardGateway, l1CustomGateway, l1WethGateway, l1ProxyAdmin, + l1Multicall, l2Router, l2StandardGateway: l2StandardGateway.address, l2CustomGateway, @@ -231,6 +237,7 @@ export const createTokenBridge = async ( l2Weth, beaconProxyFactory, l2ProxyAdmin, + l2Multicall } } diff --git a/scripts/deployment/createTokenBridge.ts b/scripts/deployment/createTokenBridge.ts index f99105e25e..5cf65f9ee9 100644 --- a/scripts/deployment/createTokenBridge.ts +++ b/scripts/deployment/createTokenBridge.ts @@ -77,7 +77,7 @@ export const createTokenBridgeOnTargetChain = async () => { l1CustomGateway: deployedContracts.l1CustomGateway, l1ERC20Gateway: deployedContracts.l1StandardGateway, l1GatewayRouter: deployedContracts.l1Router, - l1MultiCall: '', + l1MultiCall: deployedContracts.l1Multicall, l1ProxyAdmin: deployedContracts.l1ProxyAdmin, l1Weth: deployedContracts.l1Weth, l1WethGateway: deployedContracts.l1WethGateway, @@ -85,7 +85,7 @@ export const createTokenBridgeOnTargetChain = async () => { l2CustomGateway: deployedContracts.l2CustomGateway, l2ERC20Gateway: deployedContracts.l2StandardGateway, l2GatewayRouter: deployedContracts.l2Router, - l2Multicall: '', + l2Multicall: deployedContracts.l2Multicall, l2ProxyAdmin: deployedContracts.l2ProxyAdmin, l2Weth: deployedContracts.l2Weth, l2WethGateway: deployedContracts.l2WethGateway, diff --git a/scripts/local-deployment/deployCreatorAndCreateTokenBridge.ts b/scripts/local-deployment/deployCreatorAndCreateTokenBridge.ts index af8435ce6c..392eb6385e 100644 --- a/scripts/local-deployment/deployCreatorAndCreateTokenBridge.ts +++ b/scripts/local-deployment/deployCreatorAndCreateTokenBridge.ts @@ -114,7 +114,7 @@ export const setupTokenBridgeInLocalEnv = async () => { l1CustomGateway: deployedContracts.l1CustomGateway, l1ERC20Gateway: deployedContracts.l1StandardGateway, l1GatewayRouter: deployedContracts.l1Router, - l1MultiCall: '', + l1MultiCall: deployedContracts.l1Multicall, l1ProxyAdmin: deployedContracts.l1ProxyAdmin, l1Weth: deployedContracts.l1Weth, l1WethGateway: deployedContracts.l1WethGateway, @@ -122,7 +122,7 @@ export const setupTokenBridgeInLocalEnv = async () => { l2CustomGateway: deployedContracts.l2CustomGateway, l2ERC20Gateway: deployedContracts.l2StandardGateway, l2GatewayRouter: deployedContracts.l2Router, - l2Multicall: '', + l2Multicall: deployedContracts.l2Multicall, l2ProxyAdmin: deployedContracts.l2ProxyAdmin, l2Weth: deployedContracts.l2Weth, l2WethGateway: deployedContracts.l2WethGateway, From 2acb1fba6aaa819b974fb4d480441794b6caa323 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Fri, 10 Nov 2023 14:24:13 +0100 Subject: [PATCH 09/48] Add ownership info --- docs/deployment.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/deployment.md b/docs/deployment.md index a56322f0ba..212eb9b3e0 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -35,6 +35,8 @@ To deploy to Arbitrum One yarn run deploy-factory --network arb1 ``` +Script output will contain all deployed addresses. + ## TokenBridgeCreator Checkout target code, install dependencies and build @@ -60,4 +62,13 @@ Note: Gas limit for deploying child chain factory via retryable needs to be prov Finally, deploy token bridge creator. Target chain is defined by `BASECHAIN_RPC` env var (no need to provide `--network` flag). ``` yarn run deploy:token-bridge-creator -``` \ No newline at end of file +``` + +Script outputs `L1TokenBridgeCreator` and `L1TokenBridgeRetryableSender` addresses. All deployed addresses can be obtained through `L1TokenBridgeCreator` contract. + + +## Ownership +These contracts will be owned by deployer: +- RollupCreator (owner can set templates) +- L1AtomicTokenBridgeCreator (owner can set templates) +- ProxyAdmin of L1AtomicTokenBridgeCreator and L1TokenBridgeRetryableSender (owner can do upgrades) \ No newline at end of file From d84a496ce6e8fa89d85e02a10051fa26ac24c70f Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Mon, 13 Nov 2023 18:11:59 +0100 Subject: [PATCH 10/48] Add .env sample for Arb1 --- .env.arb1.sample | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .env.arb1.sample diff --git a/.env.arb1.sample b/.env.arb1.sample new file mode 100644 index 0000000000..d24320e04b --- /dev/null +++ b/.env.arb1.sample @@ -0,0 +1,25 @@ +## RPC endpoint +BASECHAIN_RPC="https://arb1.arbitrum.io/rpc" + +## Deployer key used for deploying creator or creating token bridge +BASECHAIN_DEPLOYER_KEY="" + +## WETH address on the basechain. It will be set and used in the TokenBridgeCreator +BASECHAIN_WETH="0x82aF49447D8a07e3bd95BD0d56f35241523fBab1" + +## Gas limit for deploying child chain factory needs to be provided to the TokenBridgeCreator when templates are set. +## If this param is not provided then gas limit will be estimated using SDK from child chain (specified by ORBIT_RPC and ROLLUP_ADDRESS) +GAS_LIMIT_FOR_L2_FACTORY_DEPLOYMENT=6000000 + +## Contract verification +ARBISCAN_API_KEY="" + + +### Vars for creating token bridge from existing TokenBridgeCreator +## Rollup on top of which token bridge will be created +# ROLLUP_ADDRESS="" +# ORBIT_RPC="" +# ROLLUP_OWNER="" +# L1_TOKEN_BRIDGE_CREATOR="" +# # needed for verification +# L1_RETRYABLE_SENDER="" \ No newline at end of file From b8c2f877f4316925be52c7cc2c0f21bb571a4d38 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Mon, 13 Nov 2023 18:40:51 +0100 Subject: [PATCH 11/48] Remove unnecessary checks --- scripts/deployment/deployTokenBridgeCreator.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/deployment/deployTokenBridgeCreator.ts b/scripts/deployment/deployTokenBridgeCreator.ts index 900ea50e22..8d2be45d12 100644 --- a/scripts/deployment/deployTokenBridgeCreator.ts +++ b/scripts/deployment/deployTokenBridgeCreator.ts @@ -35,18 +35,18 @@ export const envVars = { * @returns */ export const deployTokenBridgeCreator = async () => { - if (!envVars.baseChainRpc || envVars.baseChainRpc == '') { + if (!envVars.baseChainRpc) { throw new Error('Missing BASECHAIN_RPC in env vars') } - if (!envVars.baseChainDeployerKey || envVars.baseChainDeployerKey == '') { + if (!envVars.baseChainDeployerKey) { throw new Error('Missing BASECHAIN_DEPLOYER_KEY in env vars') } - if (!envVars.baseChainWeth || envVars.baseChainWeth == '') { + if (!envVars.baseChainWeth) { throw new Error('Missing BASECHAIN_WETH in env vars') } if ( - (!envVars.rollupAddress || envVars.rollupAddress == '') && - (!envVars.childChainRpc || envVars.childChainRpc == '') && + !envVars.rollupAddress && + !envVars.childChainRpc && !envVars.gasLimitForL2FactoryDeployment ) { throw new Error( From a91d7fd71ae63a49741b8e4d056bba484e48f735 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Mon, 13 Nov 2023 18:50:33 +0100 Subject: [PATCH 12/48] Fix env var check --- scripts/deployment/deployTokenBridgeCreator.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/deployment/deployTokenBridgeCreator.ts b/scripts/deployment/deployTokenBridgeCreator.ts index 8d2be45d12..569ce10716 100644 --- a/scripts/deployment/deployTokenBridgeCreator.ts +++ b/scripts/deployment/deployTokenBridgeCreator.ts @@ -45,8 +45,7 @@ export const deployTokenBridgeCreator = async () => { throw new Error('Missing BASECHAIN_WETH in env vars') } if ( - !envVars.rollupAddress && - !envVars.childChainRpc && + !(envVars.rollupAddress && envVars.childChainRpc) && !envVars.gasLimitForL2FactoryDeployment ) { throw new Error( From 969242ef63cc237d42a49806a179e25b4ed69ad8 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Tue, 14 Nov 2023 12:30:17 +0100 Subject: [PATCH 13/48] Initialize logic contract on parent chain with dummy data --- scripts/atomicTokenBridgeDeployer.ts | 96 +++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 3 deletions(-) diff --git a/scripts/atomicTokenBridgeDeployer.ts b/scripts/atomicTokenBridgeDeployer.ts index 3c0b96fdee..a22b0eb514 100644 --- a/scripts/atomicTokenBridgeDeployer.ts +++ b/scripts/atomicTokenBridgeDeployer.ts @@ -41,6 +41,11 @@ import { getBaseFee } from '@arbitrum/sdk/dist/lib/utils/lib' import { RollupAdminLogic__factory } from '@arbitrum/sdk/dist/lib/abi/factories/RollupAdminLogic__factory' import { ContractVerifier } from './contractVerifier' +/** + * Dummy non-zero address which is provided to logic contracts initializers + */ +const ADDRESS_DEAD = '0x000000000000000000000000000000000000dEaD' + /** * Use already deployed L1TokenBridgeCreator to create and init token bridge contracts. * Function first gets estimates for 2 retryable tickets - one for deploying L2 factory and @@ -237,7 +242,7 @@ export const createTokenBridge = async ( l2Weth, beaconProxyFactory, l2ProxyAdmin, - l2Multicall + l2Multicall, } } @@ -304,40 +309,104 @@ export const deployL1TokenBridgeCreator = async ( l1Deployer ) + // initialize retryable sender logic contract + await (await retryableSenderLogic.initialize()).wait() + /// init creator await (await l1TokenBridgeCreator.initialize(retryableSender.address)).wait() - /// deploy L1 logic contracts + /// deploy L1 logic contracts. Initialize them with dummy data const routerTemplate = await new L1GatewayRouter__factory(l1Deployer).deploy() await routerTemplate.deployed() + await ( + await routerTemplate.initialize( + ADDRESS_DEAD, + ADDRESS_DEAD, + ADDRESS_DEAD, + ADDRESS_DEAD, + ADDRESS_DEAD + ) + ).wait() const standardGatewayTemplate = await new L1ERC20Gateway__factory( l1Deployer ).deploy() await standardGatewayTemplate.deployed() + await ( + await standardGatewayTemplate.initialize( + ADDRESS_DEAD, + ADDRESS_DEAD, + ADDRESS_DEAD, + ethers.utils.hexZeroPad('0x01', 32), + ADDRESS_DEAD + ) + ).wait() const customGatewayTemplate = await new L1CustomGateway__factory( l1Deployer ).deploy() await customGatewayTemplate.deployed() + await ( + await customGatewayTemplate.initialize( + ADDRESS_DEAD, + ADDRESS_DEAD, + ADDRESS_DEAD, + ADDRESS_DEAD + ) + ).wait() const wethGatewayTemplate = await new L1WethGateway__factory( l1Deployer ).deploy() await wethGatewayTemplate.deployed() + await ( + await wethGatewayTemplate.initialize( + ADDRESS_DEAD, + ADDRESS_DEAD, + ADDRESS_DEAD, + ADDRESS_DEAD, + ADDRESS_DEAD + ) + ).wait() const feeTokenBasedRouterTemplate = await new L1OrbitGatewayRouter__factory( l1Deployer ).deploy() await feeTokenBasedRouterTemplate.deployed() + await ( + await feeTokenBasedRouterTemplate.initialize( + ADDRESS_DEAD, + ADDRESS_DEAD, + ADDRESS_DEAD, + ADDRESS_DEAD, + ADDRESS_DEAD + ) + ).wait() const feeTokenBasedStandardGatewayTemplate = await new L1OrbitERC20Gateway__factory(l1Deployer).deploy() await feeTokenBasedStandardGatewayTemplate.deployed() + await ( + await feeTokenBasedStandardGatewayTemplate.initialize( + ADDRESS_DEAD, + ADDRESS_DEAD, + ADDRESS_DEAD, + ethers.utils.hexZeroPad('0x01', 32), + ADDRESS_DEAD + ) + ).wait() const feeTokenBasedCustomGatewayTemplate = await new L1OrbitCustomGateway__factory(l1Deployer).deploy() await feeTokenBasedCustomGatewayTemplate.deployed() + await ( + await feeTokenBasedCustomGatewayTemplate.initialize( + ADDRESS_DEAD, + ADDRESS_DEAD, + ADDRESS_DEAD, + ADDRESS_DEAD + ) + ).wait() const upgradeExecutorFactory = new ethers.ContractFactory( UpgradeExecutorABI, @@ -359,7 +428,7 @@ export const deployL1TokenBridgeCreator = async ( upgradeExecutor: upgradeExecutor.address, } - /// deploy L2 contracts as placeholders on L1 + /// deploy L2 contracts as placeholders on L1. Initialize them with dummy data const l2TokenBridgeFactoryOnL1 = await new L2AtomicTokenBridgeFactory__factory(l1Deployer).deploy() await l2TokenBridgeFactoryOnL1.deployed() @@ -368,21 +437,42 @@ export const deployL1TokenBridgeCreator = async ( l1Deployer ).deploy() await l2GatewayRouterOnL1.deployed() + await ( + await l2GatewayRouterOnL1.initialize(ADDRESS_DEAD, ADDRESS_DEAD) + ).wait() const l2StandardGatewayAddressOnL1 = await new L2ERC20Gateway__factory( l1Deployer ).deploy() await l2StandardGatewayAddressOnL1.deployed() + await ( + await l2StandardGatewayAddressOnL1.initialize( + ADDRESS_DEAD, + ADDRESS_DEAD, + ADDRESS_DEAD + ) + ).wait() const l2CustomGatewayAddressOnL1 = await new L2CustomGateway__factory( l1Deployer ).deploy() await l2CustomGatewayAddressOnL1.deployed() + await ( + await l2CustomGatewayAddressOnL1.initialize(ADDRESS_DEAD, ADDRESS_DEAD) + ).wait() const l2WethGatewayAddressOnL1 = await new L2WethGateway__factory( l1Deployer ).deploy() await l2WethGatewayAddressOnL1.deployed() + await ( + await l2WethGatewayAddressOnL1.initialize( + ADDRESS_DEAD, + ADDRESS_DEAD, + ADDRESS_DEAD, + ADDRESS_DEAD + ) + ).wait() const l2WethAddressOnL1 = await new AeWETH__factory(l1Deployer).deploy() await l2WethAddressOnL1.deployed() From 25ab00fbb8a135afefc068002b9fe62d1529592a Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Tue, 14 Nov 2023 13:03:33 +0100 Subject: [PATCH 14/48] Init child chain logic contracts with dummy values --- .../arbitrum/L2AtomicTokenBridgeFactory.sol | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol b/contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol index 4859535812..93233a871f 100644 --- a/contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol +++ b/contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol @@ -119,6 +119,10 @@ contract L2AtomicTokenBridgeFactory { Create2.deploy(0, _getL2Salt(OrbitSalts.L2_ROUTER_LOGIC), _creationCodeFor(runtimeCode)); ProxyAdmin(proxyAdmin).upgrade(ITransparentUpgradeableProxy(canonicalRouter), routerLogic); + // init logic contract with dummy values + address dead = address(0x000000000000000000000000000000000000dEaD); + L2GatewayRouter(routerLogic).initialize(dead, dead); + // init L2GatewayRouter(canonicalRouter).initialize(l1Router, l2StandardGatewayCanonicalAddress); @@ -147,6 +151,10 @@ contract L2AtomicTokenBridgeFactory { ITransparentUpgradeableProxy(canonicalStdGateway), stdGatewayLogic ); + // init logic contract with dummy values + address dead = address(0x000000000000000000000000000000000000dEaD); + L2ERC20Gateway(stdGatewayLogic).initialize(dead, dead, dead); + // create beacon StandardArbERC20 standardArbERC20 = new StandardArbERC20{ salt: _getL2Salt(OrbitSalts.L2_STANDARD_ERC20) @@ -189,8 +197,12 @@ contract L2AtomicTokenBridgeFactory { ITransparentUpgradeableProxy(canonicalCustomGateway), customGatewayLogicAddress ); + // init logic contract with dummy values + address dead = address(0x000000000000000000000000000000000000dEaD); + L2CustomGateway(customGatewayLogicAddress).initialize(dead, dead); + // init - L2GatewayRouter(canonicalCustomGateway).initialize(l1CustomGateway, router); + L2CustomGateway(canonicalCustomGateway).initialize(l1CustomGateway, router); } function _deployWethGateway( @@ -229,6 +241,10 @@ contract L2AtomicTokenBridgeFactory { ITransparentUpgradeableProxy(canonicalL2WethGateway), l2WethGatewayLogic ); + // init logic contract with dummy values + address dead = address(0x000000000000000000000000000000000000dEaD); + L2WethGateway(payable(l2WethGatewayLogic)).initialize(dead, dead, dead, dead); + // init gateway L2WethGateway(payable(canonicalL2WethGateway)).initialize( l1WethGateway, router, l1Weth, address(canonicalL2Weth) From 4d0f93bb60a1791bce6341fbeaf66f25bdd4472f Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Tue, 14 Nov 2023 18:21:58 +0100 Subject: [PATCH 15/48] Use a constant for dead address --- .../arbitrum/L2AtomicTokenBridgeFactory.sol | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol b/contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol index 93233a871f..b1d878f882 100644 --- a/contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol +++ b/contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol @@ -25,6 +25,9 @@ import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; contract L2AtomicTokenBridgeFactory { error L2AtomicTokenBridgeFactory_AlreadyExists(); + // Dummy non-zero address which is provided to logic contracts initializers. + address private constant ADDRESS_DEAD = address(0x000000000000000000000000000000000000dEaD); + function deployL2Contracts( L2RuntimeCode calldata l2Code, address l1Router, @@ -119,9 +122,8 @@ contract L2AtomicTokenBridgeFactory { Create2.deploy(0, _getL2Salt(OrbitSalts.L2_ROUTER_LOGIC), _creationCodeFor(runtimeCode)); ProxyAdmin(proxyAdmin).upgrade(ITransparentUpgradeableProxy(canonicalRouter), routerLogic); - // init logic contract with dummy values - address dead = address(0x000000000000000000000000000000000000dEaD); - L2GatewayRouter(routerLogic).initialize(dead, dead); + // init logic contract with dummy values. + L2GatewayRouter(routerLogic).initialize(ADDRESS_DEAD, ADDRESS_DEAD); // init L2GatewayRouter(canonicalRouter).initialize(l1Router, l2StandardGatewayCanonicalAddress); @@ -152,8 +154,7 @@ contract L2AtomicTokenBridgeFactory { ); // init logic contract with dummy values - address dead = address(0x000000000000000000000000000000000000dEaD); - L2ERC20Gateway(stdGatewayLogic).initialize(dead, dead, dead); + L2ERC20Gateway(stdGatewayLogic).initialize(ADDRESS_DEAD, ADDRESS_DEAD, ADDRESS_DEAD); // create beacon StandardArbERC20 standardArbERC20 = new StandardArbERC20{ @@ -198,8 +199,7 @@ contract L2AtomicTokenBridgeFactory { ); // init logic contract with dummy values - address dead = address(0x000000000000000000000000000000000000dEaD); - L2CustomGateway(customGatewayLogicAddress).initialize(dead, dead); + L2CustomGateway(customGatewayLogicAddress).initialize(ADDRESS_DEAD, ADDRESS_DEAD); // init L2CustomGateway(canonicalCustomGateway).initialize(l1CustomGateway, router); @@ -242,8 +242,9 @@ contract L2AtomicTokenBridgeFactory { ); // init logic contract with dummy values - address dead = address(0x000000000000000000000000000000000000dEaD); - L2WethGateway(payable(l2WethGatewayLogic)).initialize(dead, dead, dead, dead); + L2WethGateway(payable(l2WethGatewayLogic)).initialize( + ADDRESS_DEAD, ADDRESS_DEAD, ADDRESS_DEAD, ADDRESS_DEAD + ); // init gateway L2WethGateway(payable(canonicalL2WethGateway)).initialize( From 127c1f3d301a173a64dc27040df394a10938dbb3 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Tue, 14 Nov 2023 18:44:40 +0100 Subject: [PATCH 16/48] Add comments about calling initializers --- .../arbitrum/L2AtomicTokenBridgeFactory.sol | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol b/contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol index b1d878f882..1aa927ad28 100644 --- a/contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol +++ b/contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol @@ -25,7 +25,10 @@ import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; contract L2AtomicTokenBridgeFactory { error L2AtomicTokenBridgeFactory_AlreadyExists(); - // Dummy non-zero address which is provided to logic contracts initializers. + // In order to avoid having uninitialized logic contracts, `initialize` function will be called + // on all logic contracts which don't have initializers disabled. This dummy non-zero address + // will be provided to those initializers, as values written to the logic contract's storage + // are not used. address private constant ADDRESS_DEAD = address(0x000000000000000000000000000000000000dEaD); function deployL2Contracts( @@ -89,7 +92,8 @@ contract L2AtomicTokenBridgeFactory { proxyAdmin, _getL2Salt(OrbitSalts.L2_EXECUTOR), _getL2Salt(OrbitSalts.L2_EXECUTOR_LOGIC) ); - // create UpgradeExecutor logic and upgrade to it + // Create UpgradeExecutor logic and upgrade to it. + // Note: UpgradeExecutor logic has initializer disabled so no need to call it. address upExecutorLogic = Create2.deploy( 0, _getL2Salt(OrbitSalts.L2_EXECUTOR_LOGIC), _creationCodeFor(runtimeCode) ); @@ -218,7 +222,7 @@ contract L2AtomicTokenBridgeFactory { proxyAdmin, _getL2Salt(OrbitSalts.L2_WETH), _getL2Salt(OrbitSalts.L2_WETH_LOGIC) ); - // create L2WETH logic and upgrade + // Create L2WETH logic and upgrade. Note: L2WETH logic has initializer disabled so no need to call it. address l2WethLogic = Create2.deploy( 0, _getL2Salt(OrbitSalts.L2_WETH_LOGIC), _creationCodeFor(aeWethRuntimeCode) ); From 0dfa3292737d81f84ac77090b2abb45be3b920a3 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Wed, 15 Nov 2023 09:06:27 +0100 Subject: [PATCH 17/48] Init l2WETH logic contract --- .../tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol b/contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol index 1aa927ad28..4b0c2b8703 100644 --- a/contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol +++ b/contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol @@ -222,7 +222,7 @@ contract L2AtomicTokenBridgeFactory { proxyAdmin, _getL2Salt(OrbitSalts.L2_WETH), _getL2Salt(OrbitSalts.L2_WETH_LOGIC) ); - // Create L2WETH logic and upgrade. Note: L2WETH logic has initializer disabled so no need to call it. + // Create L2WETH logic and upgrade address l2WethLogic = Create2.deploy( 0, _getL2Salt(OrbitSalts.L2_WETH_LOGIC), _creationCodeFor(aeWethRuntimeCode) ); @@ -255,6 +255,9 @@ contract L2AtomicTokenBridgeFactory { l1WethGateway, router, l1Weth, address(canonicalL2Weth) ); + // init logic contract with dummy values + aeWETH(payable(l2WethLogic)).initialize("", "", 0, ADDRESS_DEAD, ADDRESS_DEAD); + // init L2Weth aeWETH(payable(canonicalL2Weth)).initialize( "WETH", "WETH", 18, canonicalL2WethGateway, l1Weth From a0883f9ecc7625341464b93ea086e5bd16f87a2d Mon Sep 17 00:00:00 2001 From: gzeon Date: Fri, 17 Nov 2023 00:32:11 +0800 Subject: [PATCH 18/48] release: v1.1.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 48b5c78da6..cabb46c962 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@arbitrum/token-bridge-contracts", - "version": "1.1.0", + "version": "1.1.1", "license": "Apache-2.0", "scripts": { "prepublishOnly": "hardhat clean && hardhat compile", From ea32799f0253dbffc349e36738c8579225e54aee Mon Sep 17 00:00:00 2001 From: gzeon Date: Tue, 5 Dec 2023 19:38:45 +0900 Subject: [PATCH 19/48] fix: blockscout verification --- .../arbitrum/L2AtomicTokenBridgeFactory.sol | 59 +++++++++---------- .../ethereum/L1AtomicTokenBridgeCreator.sol | 43 +++++--------- .../libraries/CreationCodeHelper.sol | 24 ++++++++ .../tokenbridge/test/CreationCodeTest.sol | 13 ++++ 4 files changed, 79 insertions(+), 60 deletions(-) create mode 100644 contracts/tokenbridge/libraries/CreationCodeHelper.sol create mode 100644 contracts/tokenbridge/test/CreationCodeTest.sol diff --git a/contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol b/contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol index 4b0c2b8703..8d563f5c60 100644 --- a/contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol +++ b/contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol @@ -7,6 +7,7 @@ import {L2CustomGateway} from "./gateway/L2CustomGateway.sol"; import {L2WethGateway} from "./gateway/L2WethGateway.sol"; import {StandardArbERC20} from "./StandardArbERC20.sol"; import {IUpgradeExecutor} from "@offchainlabs/upgrade-executor/src/IUpgradeExecutor.sol"; +import {CreationCodeHelper} from "../libraries/CreationCodeHelper.sol"; import {BeaconProxyFactory} from "../libraries/ClonableBeaconProxy.sol"; import {aeWETH} from "../libraries/aeWETH.sol"; import {UpgradeableBeacon} from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; @@ -75,7 +76,11 @@ contract L2AtomicTokenBridgeFactory { } // deploy multicall - Create2.deploy(0, _getL2Salt(OrbitSalts.L2_MULTICALL), _creationCodeFor(l2Code.multicall)); + Create2.deploy( + 0, + _getL2Salt(OrbitSalts.L2_MULTICALL), + CreationCodeHelper.getCreationCodeFor(l2Code.multicall) + ); // transfer ownership to L2 upgradeExecutor ProxyAdmin(proxyAdmin).transferOwnership(upgradeExecutor); @@ -93,14 +98,19 @@ contract L2AtomicTokenBridgeFactory { ); // Create UpgradeExecutor logic and upgrade to it. - // Note: UpgradeExecutor logic has initializer disabled so no need to call it. address upExecutorLogic = Create2.deploy( - 0, _getL2Salt(OrbitSalts.L2_EXECUTOR_LOGIC), _creationCodeFor(runtimeCode) + 0, + _getL2Salt(OrbitSalts.L2_EXECUTOR_LOGIC), + CreationCodeHelper.getCreationCodeFor(runtimeCode) ); ProxyAdmin(proxyAdmin).upgrade( ITransparentUpgradeableProxy(canonicalUpgradeExecutor), upExecutorLogic ); + // init logic contract with dummy values + address[] memory empty = new address[](0); + IUpgradeExecutor(upExecutorLogic).initialize(ADDRESS_DEAD, empty); + // init upgrade executor address[] memory executors = new address[](2); executors[0] = rollupOwner; @@ -122,8 +132,11 @@ contract L2AtomicTokenBridgeFactory { ); // create L2 router logic and upgrade - address routerLogic = - Create2.deploy(0, _getL2Salt(OrbitSalts.L2_ROUTER_LOGIC), _creationCodeFor(runtimeCode)); + address routerLogic = Create2.deploy( + 0, + _getL2Salt(OrbitSalts.L2_ROUTER_LOGIC), + CreationCodeHelper.getCreationCodeFor(runtimeCode) + ); ProxyAdmin(proxyAdmin).upgrade(ITransparentUpgradeableProxy(canonicalRouter), routerLogic); // init logic contract with dummy values. @@ -151,7 +164,9 @@ contract L2AtomicTokenBridgeFactory { // create L2 standard gateway logic and upgrade address stdGatewayLogic = Create2.deploy( - 0, _getL2Salt(OrbitSalts.L2_STANDARD_GATEWAY_LOGIC), _creationCodeFor(runtimeCode) + 0, + _getL2Salt(OrbitSalts.L2_STANDARD_GATEWAY_LOGIC), + CreationCodeHelper.getCreationCodeFor(runtimeCode) ); ProxyAdmin(proxyAdmin).upgrade( ITransparentUpgradeableProxy(canonicalStdGateway), stdGatewayLogic @@ -196,7 +211,9 @@ contract L2AtomicTokenBridgeFactory { // create L2 custom gateway logic and upgrade address customGatewayLogicAddress = Create2.deploy( - 0, _getL2Salt(OrbitSalts.L2_CUSTOM_GATEWAY_LOGIC), _creationCodeFor(runtimeCode) + 0, + _getL2Salt(OrbitSalts.L2_CUSTOM_GATEWAY_LOGIC), + CreationCodeHelper.getCreationCodeFor(runtimeCode) ); ProxyAdmin(proxyAdmin).upgrade( ITransparentUpgradeableProxy(canonicalCustomGateway), customGatewayLogicAddress @@ -224,7 +241,9 @@ contract L2AtomicTokenBridgeFactory { // Create L2WETH logic and upgrade address l2WethLogic = Create2.deploy( - 0, _getL2Salt(OrbitSalts.L2_WETH_LOGIC), _creationCodeFor(aeWethRuntimeCode) + 0, + _getL2Salt(OrbitSalts.L2_WETH_LOGIC), + CreationCodeHelper.getCreationCodeFor(aeWethRuntimeCode) ); ProxyAdmin(proxyAdmin).upgrade(ITransparentUpgradeableProxy(canonicalL2Weth), l2WethLogic); @@ -239,7 +258,7 @@ contract L2AtomicTokenBridgeFactory { address l2WethGatewayLogic = Create2.deploy( 0, _getL2Salt(OrbitSalts.L2_WETH_GATEWAY_LOGIC), - _creationCodeFor(wethGatewayRuntimeCode) + CreationCodeHelper.getCreationCodeFor(wethGatewayRuntimeCode) ); ProxyAdmin(proxyAdmin).upgrade( ITransparentUpgradeableProxy(canonicalL2WethGateway), l2WethGatewayLogic @@ -288,27 +307,6 @@ contract L2AtomicTokenBridgeFactory { ) ); } - - /** - * @notice Generate a creation code that results on a contract with `code` as bytecode. - * Source - https://github.com/0xsequence/sstore2/blob/master/contracts/utils/Bytecode.sol - * @param code The returning value of the resulting `creationCode` - * @return creationCode (constructor) for new contract - */ - function _creationCodeFor(bytes memory code) internal pure returns (bytes memory) { - /* - 0x00 0x63 0x63XXXXXX PUSH4 _code.length size - 0x01 0x80 0x80 DUP1 size size - 0x02 0x60 0x600e PUSH1 14 14 size size - 0x03 0x60 0x6000 PUSH1 00 0 14 size size - 0x04 0x39 0x39 CODECOPY size - 0x05 0x60 0x6000 PUSH1 00 0 size - 0x06 0xf3 0xf3 RETURN - - */ - - return abi.encodePacked(hex"63", uint32(code.length), hex"80600E6000396000F3", code); - } } /** @@ -333,7 +331,6 @@ struct L2RuntimeCode { * Collection of salts used in CREATE2 deployment of L2 token bridge contracts. */ library OrbitSalts { - bytes public constant L1_PROXY_ADMIN = bytes("OrbitL1ProxyAdmin"); bytes public constant L1_ROUTER = bytes("OrbitL1GatewayRouterProxy"); bytes public constant L1_STANDARD_GATEWAY = bytes("OrbitL1StandardGatewayProxy"); bytes public constant L1_CUSTOM_GATEWAY = bytes("OrbitL1CustomGatewayProxy"); diff --git a/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol b/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol index 9a2fa9ddec..632df002fa 100644 --- a/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol +++ b/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol @@ -24,6 +24,7 @@ import { L2RuntimeCode, ProxyAdmin } from "../arbitrum/L2AtomicTokenBridgeFactory.sol"; +import {CreationCodeHelper} from "../libraries/CreationCodeHelper.sol"; import {BytesLib} from "../libraries/BytesLib.sol"; import { IUpgradeExecutor, @@ -57,6 +58,7 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { error L1AtomicTokenBridgeCreator_TemplatesNotSet(); error L1AtomicTokenBridgeCreator_RollupOwnershipMisconfig(); error L1AtomicTokenBridgeCreator_ProxyAdminNotFound(); + error L1AtomicTokenBridgeCreator_L2FactoryCannotBeChanged(); event OrbitTokenBridgeCreated( address indexed inbox, @@ -122,7 +124,8 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { constructor(address _l2MulticallTemplate) { l2MulticallTemplate = _l2MulticallTemplate; - ARB_MULTICALL_CODE_HASH = keccak256(_creationCodeFor(l2MulticallTemplate.code)); + ARB_MULTICALL_CODE_HASH = + keccak256(CreationCodeHelper.getCreationCodeFor(l2MulticallTemplate.code)); _disableInitializers(); } @@ -156,6 +159,12 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { ) external onlyOwner { l1Templates = _l1Templates; + if ( + l2TokenBridgeFactoryTemplate != address(0) + && l2TokenBridgeFactoryTemplate != _l2TokenBridgeFactoryTemplate + ) { + revert L1AtomicTokenBridgeCreator_L2FactoryCannotBeChanged(); + } l2TokenBridgeFactoryTemplate = _l2TokenBridgeFactoryTemplate; l2RouterTemplate = _l2RouterTemplate; l2StandardGatewayTemplate = _l2StandardGatewayTemplate; @@ -413,7 +422,8 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { returns (uint256) { // encode L2 factory bytecode - bytes memory deploymentData = _creationCodeFor(l2TokenBridgeFactoryTemplate.code); + bytes memory deploymentData = + CreationCodeHelper.getCreationCodeFor(l2TokenBridgeFactoryTemplate.code); if (isUsingFeeToken) { // transfer fee tokens to inbox to pay for 1st retryable @@ -518,11 +528,7 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { } function getCanonicalL1RouterAddress(address inbox) public view returns (address) { - address expectedL1ProxyAdminAddress = Create2.computeAddress( - _getL1Salt(OrbitSalts.L1_PROXY_ADMIN, inbox), - keccak256(type(ProxyAdmin).creationCode), - address(this) - ); + address proxyAdminAddress = IInbox_ProxyAdmin(inbox).getProxyAdmin(); bool isUsingFeeToken = _getFeeToken(inbox) != address(0); address template = isUsingFeeToken @@ -534,7 +540,7 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { keccak256( abi.encodePacked( type(TransparentUpgradeableProxy).creationCode, - abi.encode(template, expectedL1ProxyAdminAddress, bytes("")) + abi.encode(template, proxyAdminAddress, bytes("")) ) ), address(this) @@ -661,27 +667,6 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { return address(uint160(uint256(keccak256(data)))); } - /** - * @notice Generate a creation code that results on a contract with `code` as bytecode. - * Source - https://github.com/0xsequence/sstore2/blob/master/contracts/utils/Bytecode.sol - * @param code The returning value of the resulting `creationCode` - * @return creationCode (constructor) for new contract - */ - function _creationCodeFor(bytes memory code) internal pure returns (bytes memory) { - /* - 0x00 0x63 0x63XXXXXX PUSH4 _code.length size - 0x01 0x80 0x80 DUP1 size size - 0x02 0x60 0x600e PUSH1 14 14 size size - 0x03 0x60 0x6000 PUSH1 00 0 14 size size - 0x04 0x39 0x39 CODECOPY size - 0x05 0x60 0x6000 PUSH1 00 0 size - 0x06 0xf3 0xf3 RETURN - - */ - - return abi.encodePacked(hex"63", uint32(code.length), hex"80600E6000396000F3", code); - } - /** * @notice L2 contracts are deployed as proxy with dummy seed logic contracts using CREATE2. That enables * us to upfront calculate the expected canonical addresses. diff --git a/contracts/tokenbridge/libraries/CreationCodeHelper.sol b/contracts/tokenbridge/libraries/CreationCodeHelper.sol new file mode 100644 index 0000000000..1e3596ba2c --- /dev/null +++ b/contracts/tokenbridge/libraries/CreationCodeHelper.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +library CreationCodeHelper { + /** + * @notice Generate a creation code that results with a contract with `code` as deployed code. + * Generated creation code shall match the one generated by Solidity compiler with an empty constructor. + * @dev Prepended constructor bytecode consists of: + * - 608060405234801561001057600080fd5b50 - store free memory pointer, then check no callvalue is provided + * - 61xxxx - push 2 bytes of `code` length + * - 806100206000396000f3fe - copy deployed code to memory and return the location of it + * @param runtimeCode Deployed bytecode to which constructor bytecode will be prepended + * @return Creation code of a new contract + */ + function getCreationCodeFor(bytes memory runtimeCode) internal pure returns (bytes memory) { + return abi.encodePacked( + hex"608060405234801561001057600080fd5b50", + hex"61", + uint16(runtimeCode.length), + hex"806100206000396000f3fe", + runtimeCode + ); + } +} diff --git a/contracts/tokenbridge/test/CreationCodeTest.sol b/contracts/tokenbridge/test/CreationCodeTest.sol new file mode 100644 index 0000000000..ac83ebc3e8 --- /dev/null +++ b/contracts/tokenbridge/test/CreationCodeTest.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +import {CreationCodeHelper} from "../libraries/CreationCodeHelper.sol"; + +contract CreationCodeTest { + /** + * @dev Wrapper function around CreationCodeHelper.getCreationCodeFor used for testing convenience. + */ + function creationCodeFor(bytes memory code) external pure returns (bytes memory) { + return CreationCodeHelper.getCreationCodeFor(code); + } +} From 2d8c13b892b81c35af527e83a2a19b507b66d309 Mon Sep 17 00:00:00 2001 From: gzeon Date: Tue, 5 Dec 2023 19:39:23 +0900 Subject: [PATCH 20/48] test: improve coverage --- package.json | 1 + .../deployCreatorAndCreateTokenBridge.ts | 6 +- scripts/upgradeTemplate.ts | 33 +++ tasks/compareBytecode.ts | 57 +++++ test-e2e/creationCodeTest.ts | 198 ++++++++++++++++++ test-e2e/tokenBridgeDeploymentTest.ts | 139 +++++++++++- 6 files changed, 426 insertions(+), 8 deletions(-) create mode 100644 scripts/upgradeTemplate.ts create mode 100644 tasks/compareBytecode.ts create mode 100644 test-e2e/creationCodeTest.ts diff --git a/package.json b/package.json index cabb46c962..573a2187ab 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "deploy:token-bridge-creator": "ts-node ./scripts/deployment/deployTokenBridgeCreator.ts", "create:token-bridge": "ts-node ./scripts/deployment/createTokenBridge.ts", "test:tokenbridge:deployment": "hardhat test test-e2e/tokenBridgeDeploymentTest.ts", + "test:creation-code": "hardhat test test-e2e/creationCodeTest.ts", "typechain": "hardhat typechain", "deploy:tokenbridge": "hardhat run scripts/deploy_token_bridge_l1.ts --network mainnet", "gen:uml": "sol2uml ./contracts/tokenbridge/arbitrum,./contracts/tokenbridge/ethereum,./contracts/tokenbridge/libraries -o ./gatewayUML.svg", diff --git a/scripts/local-deployment/deployCreatorAndCreateTokenBridge.ts b/scripts/local-deployment/deployCreatorAndCreateTokenBridge.ts index 897ea6cc56..f1c7402d83 100644 --- a/scripts/local-deployment/deployCreatorAndCreateTokenBridge.ts +++ b/scripts/local-deployment/deployCreatorAndCreateTokenBridge.ts @@ -110,14 +110,14 @@ export const setupTokenBridgeInLocalEnv = async () => { //// run retryable estimate for deploying L2 factory const deployFactoryGasParams = await getEstimateForDeployingFactory( - l1Deployer, - l2Deployer.provider! + parentDeployer, + childDeployer.provider! ) const gasLimitForL2FactoryDeployment = deployFactoryGasParams.gasLimit const { l1TokenBridgeCreator, retryableSender } = await deployL1TokenBridgeCreator( - l1Deployer, + parentDeployer, l1Weth, gasLimitForL2FactoryDeployment ) diff --git a/scripts/upgradeTemplate.ts b/scripts/upgradeTemplate.ts new file mode 100644 index 0000000000..59fb2b8839 --- /dev/null +++ b/scripts/upgradeTemplate.ts @@ -0,0 +1,33 @@ +import { JsonRpcProvider } from '@ethersproject/providers' +import { L2AtomicTokenBridgeFactory__factory } from '../build/types' +import dotenv from 'dotenv' +import { Wallet } from 'ethers' + +dotenv.config() + +async function main() { + const deployRpc = process.env['BASECHAIN_RPC'] as string + if (deployRpc == undefined) { + throw new Error("Env var 'BASECHAIN_RPC' not set") + } + const rpc = new JsonRpcProvider(deployRpc) + + const deployKey = process.env['BASECHAIN_DEPLOYER_KEY'] as string + if (deployKey == undefined) { + throw new Error("Env var 'BASECHAIN_DEPLOYER_KEY' not set") + } + const deployer = new Wallet(deployKey).connect(rpc) + + console.log( + 'Deploying L2AtomicTokenBridgeFactory to chain', + await deployer.getChainId() + ) + const l2TokenBridgeFactory = await new L2AtomicTokenBridgeFactory__factory( + deployer + ).deploy() + await l2TokenBridgeFactory.deployed() + + console.log('l2TokenBridgeFactory:', l2TokenBridgeFactory.address) +} + +main().then(() => console.log('Done.')) diff --git a/tasks/compareBytecode.ts b/tasks/compareBytecode.ts new file mode 100644 index 0000000000..8590838c89 --- /dev/null +++ b/tasks/compareBytecode.ts @@ -0,0 +1,57 @@ +import { task } from "hardhat/config"; +import { ethers } from "ethers"; +import "@nomiclabs/hardhat-etherscan" +import { Bytecode } from "@nomiclabs/hardhat-etherscan/dist/src/solc/bytecode" +import { TASK_VERIFY_GET_CONTRACT_INFORMATION, TASK_VERIFY_GET_COMPILER_VERSIONS, TASK_VERIFY_GET_LIBRARIES } from "@nomiclabs/hardhat-etherscan/dist/src/constants" +import fs from "fs"; + +task("compareBytecode", "Compares deployed bytecode with local builds") + .addParam("contractAddrs", "A comma-separated list of deployed contract addresses") + .setAction(async ({ contractAddrs }, hre) => { + const addresses = contractAddrs.split(','); + + // Get all local contract artifact paths + const artifactPaths = await hre.artifacts.getArtifactPaths(); + + for (const contractAddr of addresses) { + + // Fetch deployed contract bytecode + const deployedBytecode = await hre.ethers.provider.getCode(contractAddr.trim()); + const deployedCodeHash = ethers.utils.keccak256(deployedBytecode); + let matchFound = false; + + for (const artifactPath of artifactPaths) { + const artifact = JSON.parse(fs.readFileSync(artifactPath, "utf8")); + if (artifact.deployedBytecode) { + const localCodeHash = ethers.utils.keccak256(artifact.deployedBytecode); + + // Compare codehashes + if (deployedCodeHash === localCodeHash) { + console.log(`Contract Address ${contractAddr.trim()} matches with ${artifact.contractName}`); + matchFound = true; + break; + } + } + } + + if (!matchFound) { + const deployedBytecodeHex = deployedBytecode.startsWith("0x") + ? deployedBytecode.slice(2) + : deployedBytecode; + try { + const info = await hre.run(TASK_VERIFY_GET_CONTRACT_INFORMATION, { + deployedBytecode: new Bytecode(deployedBytecodeHex), + matchingCompilerVersions: await hre.run( + TASK_VERIFY_GET_COMPILER_VERSIONS + ), + libraries: await hre.run(TASK_VERIFY_GET_LIBRARIES), + }) + console.log(`Contract Address ${contractAddr.trim()} matches with ${info.contractName} without checking constructor arguments`); + } catch (error) { + console.log(`No matching contract found for address ${contractAddr.trim()}`); + } + } + } + }); + +export default {}; diff --git a/test-e2e/creationCodeTest.ts b/test-e2e/creationCodeTest.ts new file mode 100644 index 0000000000..30b6048c76 --- /dev/null +++ b/test-e2e/creationCodeTest.ts @@ -0,0 +1,198 @@ +import hre, { ethers } from 'hardhat' +import { expect } from 'chai' +import { JsonRpcProvider } from '@ethersproject/providers' +import { + CreationCodeTest, + CreationCodeTest__factory, + L1AtomicTokenBridgeCreator, + L1AtomicTokenBridgeCreator__factory, +} from '../build/types' +import path from 'path' +import fs from 'fs' + +const LOCALHOST_L2_RPC = 'http://localhost:8547' + +const AE_WETH_EXPECTED_CONSTRUCTOR_SIZE = 348 +const UPGRADE_EXECUTOR_EXPECTED_CONSTRUCTOR_SIZE = 242 + +let provider: JsonRpcProvider +let creationCodeTester: CreationCodeTest +let l1TokenBridgeCreator: L1AtomicTokenBridgeCreator + +/** + * This test ensures that the Solidity lib generates the same creation code as the + * compiler for the contracts which are deployed to the child chain. + * + * The reason why we perform constructor check is due to atomic token bridge creator + * implementation. Due to contract size limits, we deploy child chain templates to the + * parent chain. When token bridge is being created, parent chain creator will fetch the + * runtime bytecode of the templates and send it to the child chain via retryable tickets. + * Child chain factory will then prepend the empty-constructor bytecode to the runtime code + * and use resulting bytecode for deployment. That's why we need to ensure that those + * impacted contracts don't have any logic in their constructors, as that logic can't be + * executed when deploying to the child chain. + * + * All impacted contracts have 32 bytes of constructor bytecode which look like this: + * 608060405234801561001057600080fd5b50615e7c806100206000396000f3fe + * This constructor checks that there's no callvalue, copies the contract code to memory + * and returns it. The only place where constructor bytecode differs between contracts + * is in 61xxxx80 where xxxx is the length of the contract's bytecode. + * + * Exception are aeWETH and UpgradeExecutor contracts. Their constructors are not empty as they + * contain logic to set the logic contract to the initialized state. In our system we need to + * perform this initialization by chaild chain factory. It is important though that constructor + * for these contracts never changes. That's why we check the constructor size matches the + * expected hardcoded size. + */ +describe('creationCodeTest', () => { + before(async function () { + /// get default deployer params in local test env + provider = new ethers.providers.JsonRpcProvider(LOCALHOST_L2_RPC) + const deployerKey = ethers.utils.sha256( + ethers.utils.toUtf8Bytes('user_token_bridge_deployer') + ) + const deployer = new ethers.Wallet(deployerKey, provider) + + /// tester which implements the 'getCreationCode' lib function + const testerFactory = await new CreationCodeTest__factory(deployer).deploy() + creationCodeTester = await testerFactory.deployed() + + /// token bridge creator which has the templates stored + l1TokenBridgeCreator = await _getTokenBridgeCreator(provider) + }) + + it('compiler generated and solidity lib generated creation code should match for L2 templates', async function () { + expect(await _getCompilerGeneratedCreationCode('L2GatewayRouter')).to.be.eq( + await _getSolidityLibGeneratedCreationCode( + provider, + creationCodeTester, + await l1TokenBridgeCreator.l2RouterTemplate() + ) + ) + + expect(await _getCompilerGeneratedCreationCode('L2ERC20Gateway')).to.be.eq( + await _getSolidityLibGeneratedCreationCode( + provider, + creationCodeTester, + await l1TokenBridgeCreator.l2StandardGatewayTemplate() + ) + ) + + expect(await _getCompilerGeneratedCreationCode('L2CustomGateway')).to.be.eq( + await _getSolidityLibGeneratedCreationCode( + provider, + creationCodeTester, + await l1TokenBridgeCreator.l2CustomGatewayTemplate() + ) + ) + + expect(await _getCompilerGeneratedCreationCode('L2WethGateway')).to.be.eq( + await _getSolidityLibGeneratedCreationCode( + provider, + creationCodeTester, + await l1TokenBridgeCreator.l2WethGatewayTemplate() + ) + ) + + expect(await _getCompilerGeneratedCreationCode('ArbMulticall2')).to.be.eq( + await _getSolidityLibGeneratedCreationCode( + provider, + creationCodeTester, + await l1TokenBridgeCreator.l2MulticallTemplate() + ) + ) + + expect( + await _getCompilerGeneratedCreationCode('L2AtomicTokenBridgeFactory') + ).to.be.eq( + await _getSolidityLibGeneratedCreationCode( + provider, + creationCodeTester, + await l1TokenBridgeCreator.l2TokenBridgeFactoryTemplate() + ) + ) + }) + + it('aeWETH constructor has expected size', async function () { + const constructorBytecode = await _getConstructorBytecode('aeWETH') + const constructorBytecodeLength = _lengthInBytes(constructorBytecode) + + expect(constructorBytecodeLength).to.be.eq( + AE_WETH_EXPECTED_CONSTRUCTOR_SIZE + ) + }) + + it('UpgradeExecutor constructor has expected size', async function () { + const constructorBytecode = await _getConstructorBytecode('UpgradeExecutor') + const constructorBytecodeLength = _lengthInBytes(constructorBytecode) + + expect(constructorBytecodeLength).to.be.eq( + UPGRADE_EXECUTOR_EXPECTED_CONSTRUCTOR_SIZE + ) + }) +}) + +async function _getCompilerGeneratedCreationCode( + contractName: string +): Promise { + // get creation code generated by the compiler + const artifact = await hre.artifacts.readArtifact(contractName) + return artifact.bytecode +} + +async function _getSolidityLibGeneratedCreationCode( + provider: JsonRpcProvider, + creationCodeTester: CreationCodeTest, + templateAddress: string +) { + const runtimeCode = await provider.getCode(templateAddress) + const solidityLibGeneratedCreationCode = + await creationCodeTester.creationCodeFor(runtimeCode) + + return solidityLibGeneratedCreationCode +} + +async function _getTokenBridgeCreator( + provider: JsonRpcProvider +): Promise { + const localNetworkFile = path.join(__dirname, '..', 'network.json') + if (!fs.existsSync(localNetworkFile)) { + throw new Error("Can't find network.json file") + } + const data = JSON.parse(fs.readFileSync(localNetworkFile).toString()) + return L1AtomicTokenBridgeCreator__factory.connect( + data['l1TokenBridgeCreator'], + provider + ) +} + +/** + * Get constructor bytecode as a difference between creation and deployed bytecode + * @param contractName + * @returns + */ +async function _getConstructorBytecode(contractName: string): Promise { + const artifact = await hre.artifacts.readArtifact(contractName) + + // remove '0x' + const creationCode = artifact.bytecode.substring(2) + const runtimeCode = artifact.deployedBytecode.substring(2) + + if (!creationCode.includes(runtimeCode)) { + throw new Error( + `Error while extracting constructor bytecode for contract ${contractName}.` + ) + } + + // extract the constructor code + return creationCode.replace(runtimeCode, '') +} + +/** + * Every byte in the constructor bytecode is represented by 2 characters in hex + * @param hex + * @returns + */ +function _lengthInBytes(hex: string): number { + return hex.length / 2 +} diff --git a/test-e2e/tokenBridgeDeploymentTest.ts b/test-e2e/tokenBridgeDeploymentTest.ts index 9a65708964..01a0fdd473 100644 --- a/test-e2e/tokenBridgeDeploymentTest.ts +++ b/test-e2e/tokenBridgeDeploymentTest.ts @@ -1,10 +1,14 @@ import { JsonRpcProvider, Provider, Filter } from '@ethersproject/providers' import { + AeWETH__factory, + ArbMulticall2, + ArbMulticall2__factory, BeaconProxyFactory__factory, IERC20Bridge__factory, IInbox__factory, IOwnable__factory, IRollupCore__factory, + L1AtomicTokenBridgeCreator, L1AtomicTokenBridgeCreator__factory, L1CustomGateway, L1CustomGateway__factory, @@ -85,10 +89,15 @@ describe('tokenBridge', () => { l1RetryableSender.toLowerCase() ) + const creator = L1AtomicTokenBridgeCreator__factory.connect( + l1TokenBridgeCreator, + l1Provider + ) await checkL1RouterInitialization( L1GatewayRouter__factory.connect(l1.router, l1Provider), l1, - l2 + l2, + creator ) await checkL1StandardGatewayInitialization( @@ -103,7 +112,7 @@ describe('tokenBridge', () => { l2 ) - const usingFeeToken = await isUsingFeeToken(l1.inbox, l1Provider) + const usingFeeToken = await _isUsingFeeToken(l1.inbox, l1Provider) if (!usingFeeToken) await checkL1WethGatewayInitialization( L1WethGateway__factory.connect(l1.wethGateway, l1Provider), @@ -131,6 +140,10 @@ describe('tokenBridge', () => { l2 ) + await checkL2MulticallInitialization( + ArbMulticall2__factory.connect(l2.multicall, l2Provider) + ) + if (!usingFeeToken) { await checkL2WethGatewayInitialization( L2WethGateway__factory.connect(l2.wethGateway, l2Provider), @@ -148,6 +161,7 @@ describe('tokenBridge', () => { await checkL1Ownership(l1) await checkL2Ownership(l2) + await checkLogicContracts(usingFeeToken, l2) }) }) @@ -156,10 +170,15 @@ describe('tokenBridge', () => { async function checkL1RouterInitialization( l1Router: L1GatewayRouter, l1: L1, - l2: L2 + l2: L2, + creator: L1AtomicTokenBridgeCreator ) { console.log('checkL1RouterInitialization') + expect(l1.router.toLowerCase()).to.be.eq( + (await creator.getCanonicalL1RouterAddress(l1.inbox)).toLowerCase() + ) + expect((await l1Router.defaultGateway()).toLowerCase()).to.be.eq( l1.standardGateway.toLowerCase() ) @@ -190,6 +209,9 @@ async function checkL1StandardGatewayInitialization( expect((await l1ERC20Gateway.inbox()).toLowerCase()).to.be.eq( l1.inbox.toLowerCase() ) + expect((await l1ERC20Gateway.l2BeaconProxyFactory()).toLowerCase()).to.be.eq( + l2.beaconProxyFactory + ) expect((await l1ERC20Gateway.l2BeaconProxyFactory()).toLowerCase()).to.be.eq( ( await L2ERC20Gateway__factory.connect( @@ -375,10 +397,17 @@ async function checkL2WethGatewayInitialization( ) } +async function checkL2MulticallInitialization(l2Multicall: ArbMulticall2) { + // check l2Multicall is deployed + const l2MulticallCode = await l2Provider.getCode(l2Multicall.address) + expect(l2MulticallCode.length).to.be.gt(0) +} + async function checkL1Ownership(l1: L1) { console.log('checkL1Ownership') // check proxyAdmins + expect(await _getProxyAdmin(l1.router, l1Provider)).to.be.eq(l1.proxyAdmin) expect(await _getProxyAdmin(l1.standardGateway, l1Provider)).to.be.eq( l1.proxyAdmin @@ -411,6 +440,7 @@ async function checkL2Ownership(l2: L2) { const l2ProxyAdmin = await _getProxyAdmin(l2.router, l2Provider) // check proxyAdmins + expect(l2ProxyAdmin).to.be.eq(l2.proxyAdmin) expect(await _getProxyAdmin(l2.router, l2Provider)).to.be.eq(l2ProxyAdmin) expect(await _getProxyAdmin(l2.standardGateway, l2Provider)).to.be.eq( l2ProxyAdmin @@ -432,8 +462,63 @@ async function checkL2Ownership(l2: L2) { expect(await _getOwner(l2ProxyAdmin, l2Provider)).to.be.eq(l2.upgradeExecutor) } +async function checkLogicContracts(usingFeeToken: boolean, l2: L2) { + console.log('checkLogicContracts') + + const upgExecutorLogic = await _getLogicAddress( + l2.upgradeExecutor, + l2Provider + ) + expect(await _isInitialized(upgExecutorLogic, l2Provider)).to.be.true + + const routerLogic = await _getLogicAddress(l2.router, l2Provider) + expect( + await L2GatewayRouter__factory.connect( + routerLogic, + l2Provider + ).counterpartGateway() + ).to.be.not.eq(ethers.constants.AddressZero) + + const standardGatewayLogic = await _getLogicAddress( + l2.standardGateway, + l2Provider + ) + expect( + await L2ERC20Gateway__factory.connect( + standardGatewayLogic, + l2Provider + ).counterpartGateway() + ).to.be.not.eq(ethers.constants.AddressZero) + + const customGatewayLogic = await _getLogicAddress( + l2.customGateway, + l2Provider + ) + expect( + await L2CustomGateway__factory.connect( + customGatewayLogic, + l2Provider + ).counterpartGateway() + ).to.be.not.eq(ethers.constants.AddressZero) + + if (!usingFeeToken) { + const wethGatewayLogic = await _getLogicAddress(l2.wethGateway, l2Provider) + expect( + await L2WethGateway__factory.connect( + wethGatewayLogic, + l2Provider + ).counterpartGateway() + ).to.be.not.eq(ethers.constants.AddressZero) + + const wethLogic = await _getLogicAddress(l2.weth, l2Provider) + expect( + await AeWETH__factory.connect(wethLogic, l2Provider).l2Gateway() + ).to.be.not.eq(ethers.constants.AddressZero) + } +} + //// utils -async function isUsingFeeToken(inbox: string, l1Provider: JsonRpcProvider) { +async function _isUsingFeeToken(inbox: string, l1Provider: JsonRpcProvider) { const bridge = await IInbox__factory.connect(inbox, l1Provider).bridge() try { @@ -511,7 +596,7 @@ async function _getTokenBridgeAddresses( upgradeExecutor: upgradeExecutor.toLowerCase(), } - const usingFeeToken = await isUsingFeeToken(l1.inbox, l1Provider) + const usingFeeToken = await _isUsingFeeToken(l1.inbox, l1Provider) const chainId = await IRollupCore__factory.connect( rollupAddress, @@ -540,6 +625,17 @@ async function _getTokenBridgeAddresses( upgradeExecutor: ( await l1TokenBridgeCreator.getCanonicalL2UpgradeExecutorAddress(chainId) ).toLowerCase(), + multicall: ( + await l1TokenBridgeCreator.getCanonicalL2Multicall(chainId) + ).toLowerCase(), + proxyAdmin: ( + await l1TokenBridgeCreator.getCanonicalL2ProxyAdminAddress(chainId) + ).toLowerCase(), + beaconProxyFactory: ( + await l1TokenBridgeCreator.getCanonicalL2BeaconProxyFactoryAddress( + chainId + ) + ).toLowerCase(), } return { @@ -561,6 +657,19 @@ async function _getProxyAdmin( ).toLowerCase() } +async function _getLogicAddress( + contractAddress: string, + provider: Provider +): Promise { + return ( + await _getAddressAtStorageSlot( + contractAddress, + provider, + '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + ) + ).toLowerCase() +} + async function _getOwner( contractAddress: string, provider: Provider @@ -592,6 +701,23 @@ async function _getAddressAtStorageSlot( return ethers.utils.getAddress(formatAddress) } +/** + * Return if contracts is initialized or not. Applicable for contracts which use OpenZeppelin Initializable pattern, + * so state of initialization is stored as uint8 in storage slot 0, offset 0. + */ +async function _isInitialized( + contractAddress: string, + provider: Provider +): Promise { + const storageSlot = 0 + const storageValue = await provider.getStorageAt(contractAddress, storageSlot) + const bigNumberValue = ethers.BigNumber.from(storageValue) + + // Ethereum storage slots are 32 bytes and a uint8 is 1 byte, we mask the lower 8 bits to convert it to uint8. + const maskedValue = bigNumberValue.and(255) + return maskedValue.toNumber() == 1 +} + interface L1 { inbox: string rollupOwner: string @@ -610,4 +736,7 @@ interface L2 { wethGateway: string weth: string upgradeExecutor: string + multicall: string + proxyAdmin: string + beaconProxyFactory: string } From 6f44081b3cbfec5f1795183893a9d303bcefc045 Mon Sep 17 00:00:00 2001 From: gzeon Date: Tue, 5 Dec 2023 19:39:32 +0900 Subject: [PATCH 21/48] ci: add e2e --- .github/workflows/build-test.yml | 36 ++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index aba7a24a69..86c07c44dd 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -94,3 +94,39 @@ jobs: - name: Test Storage Layouts run: yarn run test:storage + + test-e2e: + name: Test e2e + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - uses: OffchainLabs/actions/run-nitro-test-node@main + with: + nitro-testnode-ref: bump-nitro + l3-node: true + no-token-bridge: true + + - name: Setup node/yarn + uses: actions/setup-node@v3 + with: + node-version: 16 + cache: 'yarn' + cache-dependency-path: '**/yarn.lock' + + - name: Install packages + run: yarn + + - name: Compile contracts + run: yarn build + + - name: Deploy creator and create token bridge + run: yarn deploy:local:token-bridge + + - name: Verify deployed token bridge + run: yarn test:tokenbridge:deployment + + - name: Verify creation code generation + run: yarn test:creation-code From bf9ad3d7f25c0eaf0a5f89eec7a0a370833cea16 Mon Sep 17 00:00:00 2001 From: gzeon Date: Tue, 5 Dec 2023 19:42:52 +0900 Subject: [PATCH 22/48] release: v1.1.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 573a2187ab..dc16eb10c5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@arbitrum/token-bridge-contracts", - "version": "1.1.1", + "version": "1.1.2", "license": "Apache-2.0", "scripts": { "prepublishOnly": "hardhat clean && hardhat compile", From bf16ed3b2beff40cc8f763a325445392ba97a638 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Wed, 6 Dec 2023 17:59:42 +0100 Subject: [PATCH 23/48] Add instruction for verification --- README.md | 3 +++ docs/deployment.md | 33 ++++++++++++++++++++++++++++++--- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f244316179..cabc4a1656 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,9 @@ See security audit reports [here](./audits). This repository is offered under the Apache 2.0 license. See LICENSE for details. +## Deployment +Check [this doc](./docs/deployment.md) for instructions on deployment and verification of token bridge. + ## Contact Discord - [Arbitrum](https://discord.com/invite/5KE54JwyTs) diff --git a/docs/deployment.md b/docs/deployment.md index 212eb9b3e0..7d28305315 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -1,6 +1,6 @@ # How to deploy RollupCreator and TokenBridgeCreator? -## RollupCreator +## Deploy RollupCreator RollupCreator is in nitro-contracts repo ``` cd nitro-contracts @@ -38,7 +38,7 @@ yarn run deploy-factory --network arb1 Script output will contain all deployed addresses. -## TokenBridgeCreator +## Deploy TokenBridgeCreator Checkout target code, install dependencies and build ``` cd token-bridge-contracts @@ -71,4 +71,31 @@ Script outputs `L1TokenBridgeCreator` and `L1TokenBridgeRetryableSender` address These contracts will be owned by deployer: - RollupCreator (owner can set templates) - L1AtomicTokenBridgeCreator (owner can set templates) -- ProxyAdmin of L1AtomicTokenBridgeCreator and L1TokenBridgeRetryableSender (owner can do upgrades) \ No newline at end of file +- ProxyAdmin of L1AtomicTokenBridgeCreator and L1TokenBridgeRetryableSender (owner can do upgrades) + + +## Verify token bridge deployment +There is a verification script which checks that token bridge contracts have been properly deployed and initialized. Here are steps for running it. + +Checkout target code, install dependencies and build +``` +cd token-bridge-contracts +yarn install +yarn build +``` + +Populate .env +``` +ROLLUP_ADDRESS +L1_TOKEN_BRIDGE_CREATOR +L1_RETRYABLE_SENDER +BASECHAIN_DEPLOYER_KEY +ORBIT_RPC +``` +(`L1_RETRYABLE_SENDER` address can be obtained by calling `retryableSender()` on the L1 token bridge creator) + + +Run the script +``` +yarn run test:tokenbridge:deployment +``` \ No newline at end of file From 7f8e68b4b6b83318509886905cd422a82018635b Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Wed, 6 Dec 2023 18:00:18 +0100 Subject: [PATCH 24/48] Fix etherscan verification --- scripts/atomicTokenBridgeDeployer.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/atomicTokenBridgeDeployer.ts b/scripts/atomicTokenBridgeDeployer.ts index a22b0eb514..2eacc47490 100644 --- a/scripts/atomicTokenBridgeDeployer.ts +++ b/scripts/atomicTokenBridgeDeployer.ts @@ -510,7 +510,8 @@ export const deployL1TokenBridgeCreator = async ( ) await l1Verifier.verifyWithAddress( 'l1TokenBridgeCreatorLogic', - l1TokenBridgeCreatorLogic.address + l1TokenBridgeCreatorLogic.address, + abi.encode(['address'], [l2MulticallAddressOnL1.address]) ) await l1Verifier.verifyWithAddress( 'l1TokenBridgeCreatorProxy', From ed6575df3f68bfa80df004b5ae08fc6107deb42b Mon Sep 17 00:00:00 2001 From: gzeon Date: Mon, 11 Dec 2023 16:51:27 +0800 Subject: [PATCH 25/48] chore: add BASECHAIN_RPC to verification guide --- docs/deployment.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/deployment.md b/docs/deployment.md index 7d28305315..b58bc42dd9 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -90,6 +90,7 @@ ROLLUP_ADDRESS L1_TOKEN_BRIDGE_CREATOR L1_RETRYABLE_SENDER BASECHAIN_DEPLOYER_KEY +BASECHAIN_RPC ORBIT_RPC ``` (`L1_RETRYABLE_SENDER` address can be obtained by calling `retryableSender()` on the L1 token bridge creator) @@ -98,4 +99,4 @@ ORBIT_RPC Run the script ``` yarn run test:tokenbridge:deployment -``` \ No newline at end of file +``` From 5fbaacdaf3046f2a96448c0d9b0d4394c929e0fb Mon Sep 17 00:00:00 2001 From: gzeon Date: Tue, 19 Dec 2023 16:39:02 +0800 Subject: [PATCH 26/48] feat: add regsitry and refactor --- .../arbitrum/L2AtomicTokenBridgeFactory.sol | 129 ++-- .../ethereum/L1AtomicTokenBridgeCreator.sol | 573 ++++++------------ .../ethereum/L1TokenBridgeRetryableSender.sol | 66 +- 3 files changed, 293 insertions(+), 475 deletions(-) diff --git a/contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol b/contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol index 8d563f5c60..5781afff60 100644 --- a/contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol +++ b/contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol @@ -54,8 +54,7 @@ contract L2AtomicTokenBridgeFactory { revert L2AtomicTokenBridgeFactory_AlreadyExists(); } } - address proxyAdmin = - address(new ProxyAdmin{ salt: _getL2Salt(OrbitSalts.L2_PROXY_ADMIN) }()); + address proxyAdmin = address(new ProxyAdmin{salt: _getL2Salt(OrbitSalts.L2_PROXY_ADMIN)}()); // deploy router/gateways/executor address upgradeExecutor = _deployUpgradeExecutor( @@ -93,16 +92,13 @@ contract L2AtomicTokenBridgeFactory { address aliasedL1UpgradeExecutor ) internal returns (address) { // canonical L2 upgrade executor with dummy logic - address canonicalUpgradeExecutor = _deploySeedProxy( - proxyAdmin, _getL2Salt(OrbitSalts.L2_EXECUTOR), _getL2Salt(OrbitSalts.L2_EXECUTOR_LOGIC) - ); + address canonicalUpgradeExecutor = _deploySeedProxy(proxyAdmin, OrbitSalts.L2_EXECUTOR); // Create UpgradeExecutor logic and upgrade to it. address upExecutorLogic = Create2.deploy( - 0, - _getL2Salt(OrbitSalts.L2_EXECUTOR_LOGIC), - CreationCodeHelper.getCreationCodeFor(runtimeCode) + 0, OrbitSalts.UNSALTED, CreationCodeHelper.getCreationCodeFor(runtimeCode) ); + ProxyAdmin(proxyAdmin).upgrade( ITransparentUpgradeableProxy(canonicalUpgradeExecutor), upExecutorLogic ); @@ -127,15 +123,11 @@ contract L2AtomicTokenBridgeFactory { address proxyAdmin ) internal returns (address) { // canonical L2 router with dummy logic - address canonicalRouter = _deploySeedProxy( - proxyAdmin, _getL2Salt(OrbitSalts.L2_ROUTER), _getL2Salt(OrbitSalts.L2_ROUTER_LOGIC) - ); + address canonicalRouter = _deploySeedProxy(proxyAdmin, OrbitSalts.L2_ROUTER); // create L2 router logic and upgrade address routerLogic = Create2.deploy( - 0, - _getL2Salt(OrbitSalts.L2_ROUTER_LOGIC), - CreationCodeHelper.getCreationCodeFor(runtimeCode) + 0, OrbitSalts.UNSALTED, CreationCodeHelper.getCreationCodeFor(runtimeCode) ); ProxyAdmin(proxyAdmin).upgrade(ITransparentUpgradeableProxy(canonicalRouter), routerLogic); @@ -156,17 +148,11 @@ contract L2AtomicTokenBridgeFactory { address upgradeExecutor ) internal { // canonical L2 standard gateway with dummy logic - address canonicalStdGateway = _deploySeedProxy( - proxyAdmin, - _getL2Salt(OrbitSalts.L2_STANDARD_GATEWAY), - _getL2Salt(OrbitSalts.L2_STANDARD_GATEWAY_LOGIC) - ); + address canonicalStdGateway = _deploySeedProxy(proxyAdmin, OrbitSalts.L2_STANDARD_GATEWAY); // create L2 standard gateway logic and upgrade address stdGatewayLogic = Create2.deploy( - 0, - _getL2Salt(OrbitSalts.L2_STANDARD_GATEWAY_LOGIC), - CreationCodeHelper.getCreationCodeFor(runtimeCode) + 0, OrbitSalts.UNSALTED, CreationCodeHelper.getCreationCodeFor(runtimeCode) ); ProxyAdmin(proxyAdmin).upgrade( ITransparentUpgradeableProxy(canonicalStdGateway), stdGatewayLogic @@ -176,15 +162,11 @@ contract L2AtomicTokenBridgeFactory { L2ERC20Gateway(stdGatewayLogic).initialize(ADDRESS_DEAD, ADDRESS_DEAD, ADDRESS_DEAD); // create beacon - StandardArbERC20 standardArbERC20 = new StandardArbERC20{ - salt: _getL2Salt(OrbitSalts.L2_STANDARD_ERC20) - }(); - UpgradeableBeacon beacon = new UpgradeableBeacon{ - salt: _getL2Salt(OrbitSalts.UPGRADEABLE_BEACON) - }(address(standardArbERC20)); - BeaconProxyFactory beaconProxyFactory = new BeaconProxyFactory{ - salt: _getL2Salt(OrbitSalts.BEACON_PROXY_FACTORY) - }(); + StandardArbERC20 standardArbERC20 = new StandardArbERC20{salt: OrbitSalts.UNSALTED}(); + UpgradeableBeacon beacon = + new UpgradeableBeacon{salt: OrbitSalts.UNSALTED}(address(standardArbERC20)); + BeaconProxyFactory beaconProxyFactory = + new BeaconProxyFactory{salt: _getL2Salt(OrbitSalts.BEACON_PROXY_FACTORY)}(); // init contracts beaconProxyFactory.initialize(address(beacon)); @@ -203,17 +185,11 @@ contract L2AtomicTokenBridgeFactory { address proxyAdmin ) internal { // canonical L2 custom gateway with dummy logic - address canonicalCustomGateway = _deploySeedProxy( - proxyAdmin, - _getL2Salt(OrbitSalts.L2_CUSTOM_GATEWAY), - _getL2Salt(OrbitSalts.L2_CUSTOM_GATEWAY_LOGIC) - ); + address canonicalCustomGateway = _deploySeedProxy(proxyAdmin, OrbitSalts.L2_CUSTOM_GATEWAY); // create L2 custom gateway logic and upgrade address customGatewayLogicAddress = Create2.deploy( - 0, - _getL2Salt(OrbitSalts.L2_CUSTOM_GATEWAY_LOGIC), - CreationCodeHelper.getCreationCodeFor(runtimeCode) + 0, OrbitSalts.UNSALTED, CreationCodeHelper.getCreationCodeFor(runtimeCode) ); ProxyAdmin(proxyAdmin).upgrade( ITransparentUpgradeableProxy(canonicalCustomGateway), customGatewayLogicAddress @@ -235,30 +211,20 @@ contract L2AtomicTokenBridgeFactory { address proxyAdmin ) internal { // canonical L2 WETH with dummy logic - address canonicalL2Weth = _deploySeedProxy( - proxyAdmin, _getL2Salt(OrbitSalts.L2_WETH), _getL2Salt(OrbitSalts.L2_WETH_LOGIC) - ); + address canonicalL2Weth = _deploySeedProxy(proxyAdmin, OrbitSalts.L2_WETH); // Create L2WETH logic and upgrade address l2WethLogic = Create2.deploy( - 0, - _getL2Salt(OrbitSalts.L2_WETH_LOGIC), - CreationCodeHelper.getCreationCodeFor(aeWethRuntimeCode) + 0, OrbitSalts.UNSALTED, CreationCodeHelper.getCreationCodeFor(aeWethRuntimeCode) ); ProxyAdmin(proxyAdmin).upgrade(ITransparentUpgradeableProxy(canonicalL2Weth), l2WethLogic); // canonical L2 WETH gateway with dummy logic - address canonicalL2WethGateway = _deploySeedProxy( - proxyAdmin, - _getL2Salt(OrbitSalts.L2_WETH_GATEWAY), - _getL2Salt(OrbitSalts.L2_WETH_GATEWAY_LOGIC) - ); + address canonicalL2WethGateway = _deploySeedProxy(proxyAdmin, OrbitSalts.L2_WETH_GATEWAY); // create L2WETH gateway logic and upgrade address l2WethGatewayLogic = Create2.deploy( - 0, - _getL2Salt(OrbitSalts.L2_WETH_GATEWAY_LOGIC), - CreationCodeHelper.getCreationCodeFor(wethGatewayRuntimeCode) + 0, OrbitSalts.UNSALTED, CreationCodeHelper.getCreationCodeFor(wethGatewayRuntimeCode) ); ProxyAdmin(proxyAdmin).upgrade( ITransparentUpgradeableProxy(canonicalL2WethGateway), l2WethGatewayLogic @@ -293,27 +259,18 @@ contract L2AtomicTokenBridgeFactory { } /** - * Deploys a proxy with empty logic contract in order to get deterministic address which does not depend on actual logic contract. + * Deploys a proxy with address(this) as logic in order to get deterministic address + * the proxy is salted using a salt derived from the prefix, the chainId and the sender */ - function _deploySeedProxy(address proxyAdmin, bytes32 proxySalt, bytes32 logicSalt) - internal - returns (address) - { + function _deploySeedProxy(address proxyAdmin, bytes memory prefix) internal returns (address) { return address( - new TransparentUpgradeableProxy{ salt: proxySalt }( - address(new CanonicalAddressSeed{ salt: logicSalt}()), - proxyAdmin, - bytes("") + new TransparentUpgradeableProxy{salt: _getL2Salt(prefix)}( + address(this), proxyAdmin, bytes("") ) ); } } -/** - * Dummy contract used as initial logic contract for proxies, in order to get canonical (CREATE2 based) address. Then we can upgrade to any logic without having canonical addresses impacted. - */ -contract CanonicalAddressSeed {} - /** * Placeholder for bytecode of token bridge contracts which is sent from L1 to L2 through retryable ticket. */ @@ -331,26 +288,20 @@ struct L2RuntimeCode { * Collection of salts used in CREATE2 deployment of L2 token bridge contracts. */ library OrbitSalts { - bytes public constant L1_ROUTER = bytes("OrbitL1GatewayRouterProxy"); - bytes public constant L1_STANDARD_GATEWAY = bytes("OrbitL1StandardGatewayProxy"); - bytes public constant L1_CUSTOM_GATEWAY = bytes("OrbitL1CustomGatewayProxy"); - bytes public constant L1_WETH_GATEWAY = bytes("OrbitL1WethGatewayProxy"); - - bytes public constant L2_PROXY_ADMIN = bytes("OrbitL2ProxyAdmin"); - bytes public constant L2_ROUTER_LOGIC = bytes("OrbitL2GatewayRouterLogic"); - bytes public constant L2_ROUTER = bytes("OrbitL2GatewayRouterProxy"); - bytes public constant L2_STANDARD_GATEWAY_LOGIC = bytes("OrbitL2StandardGatewayLogic"); - bytes public constant L2_STANDARD_GATEWAY = bytes("OrbitL2StandardGatewayProxy"); - bytes public constant L2_CUSTOM_GATEWAY_LOGIC = bytes("OrbitL2CustomGatewayLogic"); - bytes public constant L2_CUSTOM_GATEWAY = bytes("OrbitL2CustomGatewayProxy"); - bytes public constant L2_WETH_GATEWAY_LOGIC = bytes("OrbitL2WethGatewayLogic"); - bytes public constant L2_WETH_GATEWAY = bytes("OrbitL2WethGatewayProxy"); - bytes public constant L2_WETH_LOGIC = bytes("OrbitL2WETH"); - bytes public constant L2_WETH = bytes("OrbitL2WETHProxy"); - bytes public constant L2_STANDARD_ERC20 = bytes("OrbitStandardArbERC20"); - bytes public constant UPGRADEABLE_BEACON = bytes("OrbitUpgradeableBeacon"); - bytes public constant BEACON_PROXY_FACTORY = bytes("OrbitBeaconProxyFactory"); - bytes public constant L2_EXECUTOR_LOGIC = bytes("OrbitL2UpgradeExecutorLogic"); - bytes public constant L2_EXECUTOR = bytes("OrbitL2UpgradeExecutorProxy"); - bytes public constant L2_MULTICALL = bytes("OrbitL2Multicall"); + bytes32 internal constant UNSALTED = bytes32(0); + + bytes internal constant L1_ROUTER = bytes("L1R"); + bytes internal constant L1_STANDARD_GATEWAY = bytes("L1SGW"); + bytes internal constant L1_CUSTOM_GATEWAY = bytes("L1CGW"); + bytes internal constant L1_WETH_GATEWAY = bytes("L1WGW"); + + bytes internal constant L2_PROXY_ADMIN = bytes("L2PA"); + bytes internal constant L2_ROUTER = bytes("L2R"); + bytes internal constant L2_STANDARD_GATEWAY = bytes("L2SGW"); + bytes internal constant L2_CUSTOM_GATEWAY = bytes("L2CGW"); + bytes internal constant L2_WETH_GATEWAY = bytes("L2WGW"); + bytes internal constant L2_WETH = bytes("L2W"); + bytes internal constant BEACON_PROXY_FACTORY = bytes("L2BPF"); + bytes internal constant L2_EXECUTOR = bytes("L2E"); + bytes internal constant L2_MULTICALL = bytes("L2MC"); } diff --git a/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol b/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol index 632df002fa..3f2c042771 100644 --- a/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol +++ b/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.4; import { L1TokenBridgeRetryableSender, L1DeploymentAddresses, + L2DeploymentAddresses, RetryableParams, L2TemplateAddresses, IERC20Inbox, @@ -19,13 +20,11 @@ import {L1OrbitERC20Gateway} from "./gateway/L1OrbitERC20Gateway.sol"; import {L1OrbitCustomGateway} from "./gateway/L1OrbitCustomGateway.sol"; import { L2AtomicTokenBridgeFactory, - CanonicalAddressSeed, OrbitSalts, L2RuntimeCode, ProxyAdmin } from "../arbitrum/L2AtomicTokenBridgeFactory.sol"; import {CreationCodeHelper} from "../libraries/CreationCodeHelper.sol"; -import {BytesLib} from "../libraries/BytesLib.sol"; import { IUpgradeExecutor, UpgradeExecutor @@ -54,24 +53,24 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { using SafeERC20 for IERC20; error L1AtomicTokenBridgeCreator_OnlyRollupOwner(); - error L1AtomicTokenBridgeCreator_InvalidRouterAddr(); error L1AtomicTokenBridgeCreator_TemplatesNotSet(); error L1AtomicTokenBridgeCreator_RollupOwnershipMisconfig(); error L1AtomicTokenBridgeCreator_ProxyAdminNotFound(); error L1AtomicTokenBridgeCreator_L2FactoryCannotBeChanged(); + error L1AtomicTokenBridgeCreator_AlreadyCreated(); event OrbitTokenBridgeCreated( address indexed inbox, address indexed owner, - address router, - address standardGateway, - address customGateway, - address wethGateway, + L1DeploymentAddresses l1Deployment, + L2DeploymentAddresses l2Deployment, address proxyAdmin, address upgradeExecutor ); event OrbitTokenBridgeTemplatesUpdated(); - event NonCanonicalRouterSet(address indexed inbox, address indexed router); + event OrbitTokenBridgeDeploymentSet( + address indexed inbox, L1DeploymentAddresses l1, L2DeploymentAddresses l2 + ); struct L1Templates { L1GatewayRouter routerTemplate; @@ -84,8 +83,10 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { IUpgradeExecutor upgradeExecutor; } - // non-canonical router registry - mapping(address => address) public inboxToNonCanonicalRouter; + // use separate mapping to allow appending to the struct in the future + // and workaround some stack too deep issues + mapping(address => L1DeploymentAddresses) public inboxToL1Deployment; + mapping(address => L2DeploymentAddresses) public inboxToL2Deployment; // Hard-code gas to make sure gas limit is big enough for L2 factory deployment to succeed. // If retryable would've reverted due to too low gas limit, nonce 0 would be burned and @@ -105,6 +106,7 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { address public l2CustomGatewayTemplate; address public l2WethGatewayTemplate; address public l2WethTemplate; + address public l2MulticallTemplate; // WETH address on L1 address public l1Weth; @@ -113,19 +115,10 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { address public l1Multicall; // immutable canonical address for L2 factory - // other canonical addresses (dependent on L2 template implementations) can be fetched through `getCanonicalL2***Address` functions + // other canonical addresses (dependent on L2 template implementations) can be fetched through `_predictL2***Address` functions address public canonicalL2FactoryAddress; - // immutable ArbMulticall2 template deployed on L1 - // Note - due to contract size limits, multicall template and its bytecode hash are set in constructor as immutables - address public immutable l2MulticallTemplate; - // code hash used for calculation of L2 multicall address - bytes32 public immutable ARB_MULTICALL_CODE_HASH; - - constructor(address _l2MulticallTemplate) { - l2MulticallTemplate = _l2MulticallTemplate; - ARB_MULTICALL_CODE_HASH = - keccak256(CreationCodeHelper.getCreationCodeFor(l2MulticallTemplate.code)); + constructor() { _disableInitializers(); } @@ -137,7 +130,7 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { retryableSender.initialize(); canonicalL2FactoryAddress = - _computeAddress(AddressAliasHelper.applyL1ToL2Alias(address(this)), 0); + _computeAddressAtNonce0(AddressAliasHelper.applyL1ToL2Alias(address(this))); } /** @@ -153,6 +146,7 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { address _l2CustomGatewayTemplate, address _l2WethGatewayTemplate, address _l2WethTemplate, + address _l2MulticallTemplate, address _l1Weth, address _l1Multicall, uint256 _gasLimitForL2FactoryDeployment @@ -171,6 +165,7 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { l2CustomGatewayTemplate = _l2CustomGatewayTemplate; l2WethGatewayTemplate = _l2WethGatewayTemplate; l2WethTemplate = _l2WethTemplate; + l2MulticallTemplate = _l2MulticallTemplate; l1Weth = _l1Weth; l1Multicall = _l1Multicall; @@ -213,214 +208,187 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { revert L1AtomicTokenBridgeCreator_RollupOwnershipMisconfig(); } - uint256 rollupChainId = IRollupCore(address(IInbox(inbox).bridge().rollup())).chainId(); + if (inboxToL1Deployment[inbox].router != address(0)) { + revert L1AtomicTokenBridgeCreator_AlreadyCreated(); + } - /// deploy L1 side of token bridge + uint256 chainId = IRollupCore(address(IInbox(inbox).bridge().rollup())).chainId(); bool isUsingFeeToken = _getFeeToken(inbox) != address(0); - L1DeploymentAddresses memory l1DeploymentAddresses = - _deployL1Contracts(inbox, rollupOwner, upgradeExecutor, isUsingFeeToken, rollupChainId); - /// deploy factory and then L2 contracts through L2 factory, using 2 retryables calls - if (isUsingFeeToken) { - _deployL2Factory(inbox, gasPriceBid, isUsingFeeToken); - _deployL2ContractsUsingFeeToken( - l1DeploymentAddresses, - inbox, - maxGasForContracts, - gasPriceBid, - rollupOwner, - upgradeExecutor, - rollupChainId - ); - } else { - uint256 valueSpentForFactory = _deployL2Factory(inbox, gasPriceBid, isUsingFeeToken); - uint256 fundsRemaining = msg.value - valueSpentForFactory; - _deployL2ContractsUsingEth( - l1DeploymentAddresses, - inbox, - maxGasForContracts, - gasPriceBid, - fundsRemaining, - rollupOwner, - upgradeExecutor, - rollupChainId - ); + // store L2 addresses before deployments + L1DeploymentAddresses memory l1Deployment; + L2DeploymentAddresses memory l2Deployment; + + // store L2 addresses which are proxies + l2Deployment.router = _getProxyAddress(OrbitSalts.L2_ROUTER, chainId); + l2Deployment.standardGateway = _getProxyAddress(OrbitSalts.L2_STANDARD_GATEWAY, chainId); + l2Deployment.customGateway = _getProxyAddress(OrbitSalts.L2_CUSTOM_GATEWAY, chainId); + if (!isUsingFeeToken) { + l2Deployment.wethGateway = _getProxyAddress(OrbitSalts.L2_WETH_GATEWAY, chainId); + l2Deployment.weth = _getProxyAddress(OrbitSalts.L2_WETH, chainId); } - } + l2Deployment.upgradeExecutor = _getProxyAddress(OrbitSalts.L2_EXECUTOR, chainId); - /** - * @notice Rollup owner can override canonical router address by registering other non-canonical router. - * @dev Non-canonical router can be unregistered by re-setting it to address(0) - it makes canonical router the valid one. - */ - function setNonCanonicalRouter(address inbox, address nonCanonicalRouter) external { - if (msg.sender != IInbox(inbox).bridge().rollup().owner()) { - revert L1AtomicTokenBridgeCreator_OnlyRollupOwner(); + // store L2 addresses which are not proxies + l2Deployment.proxyAdmin = _predictL2ProxyAdminAddress(chainId); + l2Deployment.beaconProxyFactory = _predictL2BeaconProxyFactoryAddress(chainId); + l2Deployment.multicall = _predictL2Multicall(chainId); + + // deploy L1 side of token bridge + // get existing proxy admin and upgrade executor + address proxyAdmin = IInboxProxyAdmin(inbox).getProxyAdmin(); + if (proxyAdmin == address(0)) { + revert L1AtomicTokenBridgeCreator_ProxyAdminNotFound(); } - if (nonCanonicalRouter == getCanonicalL1RouterAddress(inbox)) { - revert L1AtomicTokenBridgeCreator_InvalidRouterAddr(); + + // l1 router deployment block + { + address routerTemplate = isUsingFeeToken + ? address(l1Templates.feeTokenBasedRouterTemplate) + : address(l1Templates.routerTemplate); + l1Deployment.router = _deployProxyWithSalt( + _getL1Salt(OrbitSalts.L1_ROUTER, inbox), routerTemplate, proxyAdmin + ); } - inboxToNonCanonicalRouter[inbox] = nonCanonicalRouter; - emit NonCanonicalRouterSet(inbox, nonCanonicalRouter); - } + // l1 standard gateway deployment block + { + address template = isUsingFeeToken + ? address(l1Templates.feeTokenBasedStandardGatewayTemplate) + : address(l1Templates.standardGatewayTemplate); - function getRouter(address inbox) public view returns (address) { - address nonCanonicalRouter = inboxToNonCanonicalRouter[inbox]; + L1ERC20Gateway standardGateway = L1ERC20Gateway( + _deployProxyWithSalt( + _getL1Salt(OrbitSalts.L1_STANDARD_GATEWAY, inbox), template, proxyAdmin + ) + ); - if (nonCanonicalRouter != address(0)) { - return nonCanonicalRouter; + standardGateway.initialize( + l2Deployment.standardGateway, + l1Deployment.router, + inbox, + keccak256(type(ClonableBeaconProxy).creationCode), + l2Deployment.beaconProxyFactory + ); + + l1Deployment.standardGateway = address(standardGateway); } - return getCanonicalL1RouterAddress(inbox); - } + // l1 custom gateway deployment block + { + address template = isUsingFeeToken + ? address(l1Templates.feeTokenBasedCustomGatewayTemplate) + : address(l1Templates.customGatewayTemplate); - function _deployL1Contracts( - address inbox, - address rollupOwner, - address upgradeExecutor, - bool isUsingFeeToken, - uint256 chainId - ) internal returns (L1DeploymentAddresses memory l1Addresses) { - // get existing proxy admin and upgrade executor - address proxyAdmin = IInbox_ProxyAdmin(inbox).getProxyAdmin(); - if (proxyAdmin == address(0)) { - revert L1AtomicTokenBridgeCreator_ProxyAdminNotFound(); + L1CustomGateway customGateway = L1CustomGateway( + _deployProxyWithSalt( + _getL1Salt(OrbitSalts.L1_CUSTOM_GATEWAY, inbox), template, proxyAdmin + ) + ); + + customGateway.initialize( + l2Deployment.customGateway, l1Deployment.router, inbox, upgradeExecutor + ); + + l1Deployment.customGateway = address(customGateway); } - // deploy router - address routerTemplate = isUsingFeeToken - ? address(l1Templates.feeTokenBasedRouterTemplate) - : address(l1Templates.routerTemplate); - l1Addresses.router = address( - new TransparentUpgradeableProxy{ salt: _getL1Salt(OrbitSalts.L1_ROUTER, inbox) }( - routerTemplate, - proxyAdmin, - bytes("") - ) - ); + // l1 weth gateway deployment block + if (!isUsingFeeToken) { + L1WethGateway wethGateway = L1WethGateway( + payable( + _deployProxyWithSalt( + _getL1Salt(OrbitSalts.L1_WETH_GATEWAY, inbox), + address(l1Templates.wethGatewayTemplate), + proxyAdmin + ) + ) + ); - // deploy and init gateways - l1Addresses.standardGateway = _deployL1StandardGateway( - proxyAdmin, l1Addresses.router, inbox, isUsingFeeToken, chainId - ); - l1Addresses.customGateway = _deployL1CustomGateway( - proxyAdmin, l1Addresses.router, inbox, upgradeExecutor, isUsingFeeToken, chainId - ); - l1Addresses.wethGateway = isUsingFeeToken - ? address(0) - : _deployL1WethGateway(proxyAdmin, l1Addresses.router, inbox, chainId); - l1Addresses.weth = isUsingFeeToken ? address(0) : l1Weth; + wethGateway.initialize( + l2Deployment.wethGateway, l1Deployment.router, inbox, l1Weth, l2Deployment.weth + ); + + l1Deployment.wethGateway = address(wethGateway); + l1Deployment.weth = l1Weth; + } // init router - L1GatewayRouter(l1Addresses.router).initialize( - upgradeExecutor, - l1Addresses.standardGateway, - address(0), - getCanonicalL2RouterAddress(chainId), - inbox + L1GatewayRouter(l1Deployment.router).initialize( + upgradeExecutor, l1Deployment.standardGateway, address(0), l2Deployment.router, inbox ); - // emit it - emit OrbitTokenBridgeCreated( - inbox, + // deploy factory and then L2 contracts through L2 factory, using 2 retryables calls + _deployL2Factory(inbox, gasPriceBid, isUsingFeeToken); + if (isUsingFeeToken) { + // transfer fee tokens to inbox to pay for 2nd retryable + address feeToken = _getFeeToken(inbox); + uint256 fee = maxGasForContracts * gasPriceBid; + IERC20(feeToken).safeTransferFrom(msg.sender, inbox, fee); + } + // sweep the balance to send the retryable and refund the difference + // it is known that any eth previously in this contract can be extracted + // tho it is not expected that this contract will have any eth + retryableSender.sendRetryable{value: isUsingFeeToken ? 0 : address(this).balance}( + RetryableParams( + inbox, + canonicalL2FactoryAddress, + msg.sender, + msg.sender, + maxGasForContracts, + gasPriceBid + ), + L2TemplateAddresses( + l2RouterTemplate, + l2StandardGatewayTemplate, + l2CustomGatewayTemplate, + isUsingFeeToken ? address(0) : l2WethGatewayTemplate, + isUsingFeeToken ? address(0) : l2WethTemplate, + address(l1Templates.upgradeExecutor), + l2MulticallTemplate + ), + l1Deployment, + l2Deployment.standardGateway, rollupOwner, - l1Addresses.router, - l1Addresses.standardGateway, - l1Addresses.customGateway, - l1Addresses.wethGateway, - proxyAdmin, - upgradeExecutor - ); - } - - function _deployL1StandardGateway( - address proxyAdmin, - address router, - address inbox, - bool isUsingFeeToken, - uint256 chainId - ) internal returns (address) { - address template = isUsingFeeToken - ? address(l1Templates.feeTokenBasedStandardGatewayTemplate) - : address(l1Templates.standardGatewayTemplate); - - L1ERC20Gateway standardGateway = L1ERC20Gateway( - address( - new TransparentUpgradeableProxy{ - salt: _getL1Salt(OrbitSalts.L1_STANDARD_GATEWAY, inbox) - }(template, proxyAdmin, bytes("")) - ) + msg.sender, + AddressAliasHelper.applyL1ToL2Alias(upgradeExecutor), + isUsingFeeToken ); - standardGateway.initialize( - getCanonicalL2StandardGatewayAddress(chainId), - router, - inbox, - keccak256(type(ClonableBeaconProxy).creationCode), - getCanonicalL2BeaconProxyFactoryAddress(chainId) + emit OrbitTokenBridgeCreated( + inbox, rollupOwner, l1Deployment, l2Deployment, proxyAdmin, upgradeExecutor ); - - return address(standardGateway); + inboxToL1Deployment[inbox] = l1Deployment; + inboxToL2Deployment[inbox] = l2Deployment; } - function _deployL1CustomGateway( - address proxyAdmin, - address router, + /** + * @notice Rollup owner can override deployment + */ + function setDeployment( address inbox, - address upgradeExecutor, - bool isUsingFeeToken, - uint256 chainId - ) internal returns (address) { - address template = isUsingFeeToken - ? address(l1Templates.feeTokenBasedCustomGatewayTemplate) - : address(l1Templates.customGatewayTemplate); - - L1CustomGateway customGateway = L1CustomGateway( - address( - new TransparentUpgradeableProxy{ - salt: _getL1Salt(OrbitSalts.L1_CUSTOM_GATEWAY, inbox) - }(template, proxyAdmin, bytes("")) - ) - ); - - customGateway.initialize( - getCanonicalL2CustomGatewayAddress(chainId), router, inbox, upgradeExecutor - ); + L1DeploymentAddresses memory l1Deployment, + L2DeploymentAddresses memory l2Deployment + ) external { + if (msg.sender != IInbox(inbox).bridge().rollup().owner()) { + revert L1AtomicTokenBridgeCreator_OnlyRollupOwner(); + } - return address(customGateway); + inboxToL1Deployment[inbox] = l1Deployment; + inboxToL2Deployment[inbox] = l2Deployment; + emit OrbitTokenBridgeDeploymentSet(inbox, l1Deployment, l2Deployment); } - function _deployL1WethGateway( - address proxyAdmin, - address router, - address inbox, - uint256 chainId - ) internal returns (address) { - L1WethGateway wethGateway = L1WethGateway( - payable( - address( - new TransparentUpgradeableProxy{ - salt: _getL1Salt(OrbitSalts.L1_WETH_GATEWAY, inbox) - }(address(l1Templates.wethGatewayTemplate), proxyAdmin, bytes("")) - ) - ) - ); - - wethGateway.initialize( - getCanonicalL2WethGatewayAddress(chainId), - router, - inbox, - l1Weth, - getCanonicalL2WethAddress(chainId) - ); - - return address(wethGateway); + /** + * @notice Get the L1 router address for a given inbox + * @dev This is kept since its cheaper than accessing the mapping getter + * and is useful enough for most onchain purposes + */ + function getRouter(address inbox) public view returns (address) { + return inboxToL1Deployment[inbox].router; } - function _deployL2Factory(address inbox, uint256 gasPriceBid, bool isUsingFeeToken) - internal - returns (uint256) - { + function _deployL2Factory(address inbox, uint256 gasPriceBid, bool isUsingFeeToken) internal { // encode L2 factory bytecode bytes memory deploymentData = CreationCodeHelper.getCreationCodeFor(l2TokenBridgeFactoryTemplate.code); @@ -442,7 +410,6 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { retryableFee, deploymentData ); - return 0; } else { uint256 maxSubmissionCost = IInbox(inbox).calculateRetryableSubmissionFee(deploymentData.length, 0); @@ -458,136 +425,10 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { gasPriceBid, deploymentData ); - return retryableFee; } } - function _deployL2ContractsUsingEth( - L1DeploymentAddresses memory l1Addresses, - address inbox, - uint256 maxGas, - uint256 gasPriceBid, - uint256 availableFunds, - address rollupOwner, - address upgradeExecutor, - uint256 chainId - ) internal { - retryableSender.sendRetryableUsingEth{value: availableFunds}( - RetryableParams( - inbox, canonicalL2FactoryAddress, msg.sender, msg.sender, maxGas, gasPriceBid - ), - L2TemplateAddresses( - l2RouterTemplate, - l2StandardGatewayTemplate, - l2CustomGatewayTemplate, - l2WethGatewayTemplate, - l2WethTemplate, - address(l1Templates.upgradeExecutor), - l2MulticallTemplate - ), - l1Addresses, - getCanonicalL2StandardGatewayAddress(chainId), - rollupOwner, - msg.sender, - AddressAliasHelper.applyL1ToL2Alias(upgradeExecutor) - ); - } - - function _deployL2ContractsUsingFeeToken( - L1DeploymentAddresses memory l1Addresses, - address inbox, - uint256 maxGas, - uint256 gasPriceBid, - address rollupOwner, - address upgradeExecutor, - uint256 chainId - ) internal { - // transfer fee tokens to inbox to pay for 2nd retryable - address feeToken = _getFeeToken(inbox); - uint256 fee = maxGas * gasPriceBid; - IERC20(feeToken).safeTransferFrom(msg.sender, inbox, fee); - - retryableSender.sendRetryableUsingFeeToken( - RetryableParams( - inbox, canonicalL2FactoryAddress, msg.sender, msg.sender, maxGas, gasPriceBid - ), - L2TemplateAddresses( - l2RouterTemplate, - l2StandardGatewayTemplate, - l2CustomGatewayTemplate, - address(0), - address(0), - address(l1Templates.upgradeExecutor), - l2MulticallTemplate - ), - l1Addresses, - getCanonicalL2StandardGatewayAddress(chainId), - rollupOwner, - AddressAliasHelper.applyL1ToL2Alias(upgradeExecutor) - ); - } - - function getCanonicalL1RouterAddress(address inbox) public view returns (address) { - address proxyAdminAddress = IInbox_ProxyAdmin(inbox).getProxyAdmin(); - - bool isUsingFeeToken = _getFeeToken(inbox) != address(0); - address template = isUsingFeeToken - ? address(l1Templates.feeTokenBasedRouterTemplate) - : address(l1Templates.routerTemplate); - - return Create2.computeAddress( - _getL1Salt(OrbitSalts.L1_ROUTER, inbox), - keccak256( - abi.encodePacked( - type(TransparentUpgradeableProxy).creationCode, - abi.encode(template, proxyAdminAddress, bytes("")) - ) - ), - address(this) - ); - } - - function getCanonicalL2RouterAddress(uint256 chainId) public view returns (address) { - return _getProxyAddress( - _getL2Salt(OrbitSalts.L2_ROUTER_LOGIC, chainId), - _getL2Salt(OrbitSalts.L2_ROUTER, chainId), - chainId - ); - } - - function getCanonicalL2StandardGatewayAddress(uint256 chainId) public view returns (address) { - return _getProxyAddress( - _getL2Salt(OrbitSalts.L2_STANDARD_GATEWAY_LOGIC, chainId), - _getL2Salt(OrbitSalts.L2_STANDARD_GATEWAY, chainId), - chainId - ); - } - - function getCanonicalL2CustomGatewayAddress(uint256 chainId) public view returns (address) { - return _getProxyAddress( - _getL2Salt(OrbitSalts.L2_CUSTOM_GATEWAY_LOGIC, chainId), - _getL2Salt(OrbitSalts.L2_CUSTOM_GATEWAY, chainId), - chainId - ); - } - - function getCanonicalL2WethGatewayAddress(uint256 chainId) public view returns (address) { - return _getProxyAddress( - _getL2Salt(OrbitSalts.L2_WETH_GATEWAY_LOGIC, chainId), - _getL2Salt(OrbitSalts.L2_WETH_GATEWAY, chainId), - chainId - ); - } - - function getCanonicalL2WethAddress(uint256 chainId) public view returns (address) { - return _getProxyAddress( - _getL2Salt(OrbitSalts.L2_WETH_LOGIC, chainId), - _getL2Salt(OrbitSalts.L2_WETH, chainId), - chainId - ); - } - - function getCanonicalL2ProxyAdminAddress(uint256 chainId) public view returns (address) { + function _predictL2ProxyAdminAddress(uint256 chainId) internal view returns (address) { return Create2.computeAddress( _getL2Salt(OrbitSalts.L2_PROXY_ADMIN, chainId), keccak256(type(ProxyAdmin).creationCode), @@ -595,11 +436,7 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { ); } - function getCanonicalL2BeaconProxyFactoryAddress(uint256 chainId) - public - view - returns (address) - { + function _predictL2BeaconProxyFactoryAddress(uint256 chainId) internal view returns (address) { return Create2.computeAddress( _getL2Salt(OrbitSalts.BEACON_PROXY_FACTORY, chainId), keccak256(type(BeaconProxyFactory).creationCode), @@ -607,37 +444,25 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { ); } - function getCanonicalL2UpgradeExecutorAddress(uint256 chainId) public view returns (address) { - return _getProxyAddress( - _getL2Salt(OrbitSalts.L2_EXECUTOR_LOGIC, chainId), - _getL2Salt(OrbitSalts.L2_EXECUTOR, chainId), - chainId - ); - } - - function getCanonicalL2Multicall(uint256 chainId) public view returns (address) { + function _predictL2Multicall(uint256 chainId) internal view returns (address) { return Create2.computeAddress( _getL2Salt(OrbitSalts.L2_MULTICALL, chainId), - ARB_MULTICALL_CODE_HASH, + l2MulticallTemplate.codehash, canonicalL2FactoryAddress ); } function _getFeeToken(address inbox) internal view returns (address) { address bridge = address(IInbox(inbox).bridge()); - - (bool success, bytes memory feeTokenAddressData) = - bridge.staticcall(abi.encodeWithSelector(IERC20Bridge.nativeToken.selector)); - - if (!success || feeTokenAddressData.length < 32) { + try IERC20Bridge(bridge).nativeToken() returns (address feeToken) { + return feeToken; + } catch { return address(0); } - - return BytesLib.toAddress(feeTokenAddressData, 12); } /** - * @notice Compute address of contract deployed using CREATE opcode + * @notice Compute address of contract deployed using CREATE opcode at nonce 0 * @dev The contract address is derived by RLP encoding the deployer's address and the nonce using the Keccak-256 hashing algorithm. * More formally: keccak256(rlp.encode([origin, nonce])[12:] * @@ -647,46 +472,36 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { * - prefix of the whole list is 0xc0 + lenInBytes(RLP(list)) * After we have RLP encoding in place last step is to hash it, take last 20 bytes and cast is to an address. * + * This function is an codesize optimized version to only calculate the address for nonce 0. * @return computed address */ - function _computeAddress(address origin, uint256 nonce) internal pure returns (address) { - bytes memory data; - if (nonce == 0x00) { - data = abi.encodePacked(bytes1(0xd6), bytes1(0x94), origin, bytes1(0x80)); - } else if (nonce <= 0x7f) { - data = abi.encodePacked(bytes1(0xd6), bytes1(0x94), origin, uint8(nonce)); - } else if (nonce <= 0xff) { - data = abi.encodePacked(bytes1(0xd7), bytes1(0x94), origin, bytes1(0x81), uint8(nonce)); - } else if (nonce <= 0xffff) { - data = abi.encodePacked(bytes1(0xd8), bytes1(0x94), origin, bytes1(0x82), uint16(nonce)); - } else if (nonce <= 0xffffff) { - data = abi.encodePacked(bytes1(0xd9), bytes1(0x94), origin, bytes1(0x83), uint24(nonce)); - } else { - data = abi.encodePacked(bytes1(0xda), bytes1(0x94), origin, bytes1(0x84), uint32(nonce)); - } - return address(uint160(uint256(keccak256(data)))); + function _computeAddressAtNonce0(address origin) internal pure returns (address) { + return address( + uint160( + uint256( + keccak256(abi.encodePacked(bytes1(0xd6), bytes1(0x94), origin, bytes1(0x80))) + ) + ) + ); } /** * @notice L2 contracts are deployed as proxy with dummy seed logic contracts using CREATE2. That enables - * us to upfront calculate the expected canonical addresses. + * us to upfront calculate the expected canonical addresses. This proxy should be upgraded to the + * intended logic implementation immediately. */ - function _getProxyAddress(bytes32 logicSalt, bytes32 proxySalt, uint256 chainId) + function _getProxyAddress(bytes memory prefix, uint256 chainId) internal view returns (address) { - address logicSeedAddress = Create2.computeAddress( - logicSalt, keccak256(type(CanonicalAddressSeed).creationCode), canonicalL2FactoryAddress - ); - return Create2.computeAddress( - proxySalt, + _getL2Salt(prefix, chainId), keccak256( abi.encodePacked( type(TransparentUpgradeableProxy).creationCode, abi.encode( - logicSeedAddress, getCanonicalL2ProxyAdminAddress(chainId), bytes("") + canonicalL2FactoryAddress, _predictL2ProxyAdminAddress(chainId), bytes("") ) ) ), @@ -716,13 +531,23 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { ) ); } + + /** + * @notice Internal method to deploy TransparentUpgradeableProxy with CREATE2 opcode. + */ + function _deployProxyWithSalt(bytes32 salt, address logic, address admin) + internal + returns (address) + { + return address(new TransparentUpgradeableProxy{salt: salt}(logic, admin, bytes(""))); + } } interface IERC20Bridge { function nativeToken() external view returns (address); } -interface IInbox_ProxyAdmin { +interface IInboxProxyAdmin { function getProxyAdmin() external view returns (address); } diff --git a/contracts/tokenbridge/ethereum/L1TokenBridgeRetryableSender.sol b/contracts/tokenbridge/ethereum/L1TokenBridgeRetryableSender.sol index bde3de250b..1cba253ffc 100644 --- a/contracts/tokenbridge/ethereum/L1TokenBridgeRetryableSender.sol +++ b/contracts/tokenbridge/ethereum/L1TokenBridgeRetryableSender.sol @@ -28,6 +28,7 @@ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol */ contract L1TokenBridgeRetryableSender is Initializable, OwnableUpgradeable { error L1TokenBridgeRetryableSender_RefundFailed(); + error L1TokenBridgeRetryableSender_EthReceivedForFeeToken(); function initialize() public initializer { __Ownable_init(); @@ -36,17 +37,50 @@ contract L1TokenBridgeRetryableSender is Initializable, OwnableUpgradeable { /** * @notice Creates retryable which deploys L2 side of the token bridge. * @dev Function will build retryable data, calculate submission cost and retryable value, create retryable - * and then refund the remaining funds to original delpoyer. + * and then refund the remaining funds to original delpoyer if excess eth was sent. */ - function sendRetryableUsingEth( + function sendRetryable( RetryableParams calldata retryableParams, L2TemplateAddresses calldata l2, L1DeploymentAddresses calldata l1, address l2StandardGatewayAddress, address rollupOwner, address deployer, - address aliasedL1UpgradeExecutor + address aliasedL1UpgradeExecutor, + bool isUsingFeeToken ) external payable onlyOwner { + if (!isUsingFeeToken) { + _sendRetryableUsingEth( + retryableParams, + l2, + l1, + l2StandardGatewayAddress, + rollupOwner, + deployer, + aliasedL1UpgradeExecutor + ); + } else { + if (msg.value > 0) revert L1TokenBridgeRetryableSender_EthReceivedForFeeToken(); + _sendRetryableUsingFeeToken( + retryableParams, + l2, + l1, + l2StandardGatewayAddress, + rollupOwner, + aliasedL1UpgradeExecutor + ); + } + } + + function _sendRetryableUsingEth( + RetryableParams calldata retryableParams, + L2TemplateAddresses calldata l2, + L1DeploymentAddresses calldata l1, + address l2StandardGatewayAddress, + address rollupOwner, + address deployer, + address aliasedL1UpgradeExecutor + ) internal { bytes memory data = abi.encodeCall( L2AtomicTokenBridgeFactory.deployL2Contracts, ( @@ -77,24 +111,20 @@ contract L1TokenBridgeRetryableSender is Initializable, OwnableUpgradeable { _createRetryableUsingEth(retryableParams, maxSubmissionCost, retryableValue, data); // refund excess value to the deployer - uint256 refund = msg.value - retryableValue; - (bool success,) = deployer.call{value: refund}(""); + // it is known that any eth previously in this contract can be extracted + // tho it is not expected that this contract will have any eth + (bool success,) = deployer.call{value: address(this).balance}(""); if (!success) revert L1TokenBridgeRetryableSender_RefundFailed(); } - /** - * @notice Creates retryable which deploys L2 side of the token bridge. - * @dev Function will build retryable data, calculate submission cost and retryable value, create retryable - * and then refund the remaining funds to original delpoyer. - */ - function sendRetryableUsingFeeToken( + function _sendRetryableUsingFeeToken( RetryableParams calldata retryableParams, L2TemplateAddresses calldata l2, L1DeploymentAddresses calldata l1, address l2StandardGatewayAddress, address rollupOwner, address aliasedL1UpgradeExecutor - ) external payable onlyOwner { + ) internal { bytes memory data = abi.encodeCall( L2AtomicTokenBridgeFactory.deployL2Contracts, ( @@ -196,6 +226,18 @@ struct L1DeploymentAddresses { address weth; } +struct L2DeploymentAddresses { + address router; + address standardGateway; + address customGateway; + address wethGateway; + address weth; + address proxyAdmin; + address beaconProxyFactory; + address upgradeExecutor; + address multicall; +} + interface IERC20Inbox { function createRetryableTicket( address to, From 6d941996c5683a17bee93a13f33f31bf01032d57 Mon Sep 17 00:00:00 2001 From: gzeon Date: Tue, 19 Dec 2023 16:39:41 +0800 Subject: [PATCH 27/48] ci: e2e custom fee token Co-authored-by: Goran Vladika --- .github/workflows/build-test.yml | 37 +++ test-e2e/tokenBridgeDeploymentTest.ts | 448 +++++++++++++------------- 2 files changed, 259 insertions(+), 226 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 86c07c44dd..9c778f4e64 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -130,3 +130,40 @@ jobs: - name: Verify creation code generation run: yarn test:creation-code + + test-e2e-custom-fee-token: + name: Test e2e on custom fee token chain + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - uses: OffchainLabs/actions/run-nitro-test-node@main + with: + nitro-testnode-ref: bump-nitro + l3-node: true + args: --l3-fee-token + no-token-bridge: true + + - name: Setup node/yarn + uses: actions/setup-node@v3 + with: + node-version: 16 + cache: 'yarn' + cache-dependency-path: '**/yarn.lock' + + - name: Install packages + run: yarn + + - name: Compile contracts + run: yarn build + + - name: Deploy creator and create token bridge + run: yarn deploy:local:token-bridge + + - name: Verify deployed token bridge + run: yarn test:tokenbridge:deployment + + - name: Verify creation code generation + run: yarn test:creation-code \ No newline at end of file diff --git a/test-e2e/tokenBridgeDeploymentTest.ts b/test-e2e/tokenBridgeDeploymentTest.ts index 01a0fdd473..c6f748a99a 100644 --- a/test-e2e/tokenBridgeDeploymentTest.ts +++ b/test-e2e/tokenBridgeDeploymentTest.ts @@ -5,9 +5,9 @@ import { ArbMulticall2__factory, BeaconProxyFactory__factory, IERC20Bridge__factory, + IInboxProxyAdmin__factory, IInbox__factory, IOwnable__factory, - IRollupCore__factory, L1AtomicTokenBridgeCreator, L1AtomicTokenBridgeCreator__factory, L1CustomGateway, @@ -71,8 +71,12 @@ describe('tokenBridge', () => { } } - /// get addresses - const { l1, l2 } = await _getTokenBridgeAddresses( + console.log( + `Testing token bridge deployment for rollup ${rollupAddress} deployed by creator ${l1TokenBridgeCreator}` + ) + + /// get core contract and token bridge addresses + const { rollupAddresses, l1Deployment, l2Deployment } = await _getAddresses( rollupAddress, l1TokenBridgeCreator ) @@ -89,79 +93,87 @@ describe('tokenBridge', () => { l1RetryableSender.toLowerCase() ) - const creator = L1AtomicTokenBridgeCreator__factory.connect( - l1TokenBridgeCreator, - l1Provider - ) await checkL1RouterInitialization( - L1GatewayRouter__factory.connect(l1.router, l1Provider), - l1, - l2, - creator + L1GatewayRouter__factory.connect(l1Deployment.router, l1Provider), + l1Deployment, + l2Deployment, + rollupAddresses ) await checkL1StandardGatewayInitialization( - L1ERC20Gateway__factory.connect(l1.standardGateway, l1Provider), - l1, - l2 + L1ERC20Gateway__factory.connect(l1Deployment.standardGateway, l1Provider), + l1Deployment, + l2Deployment, + rollupAddresses ) await checkL1CustomGatewayInitialization( - L1CustomGateway__factory.connect(l1.customGateway, l1Provider), - l1, - l2 + L1CustomGateway__factory.connect(l1Deployment.customGateway, l1Provider), + l1Deployment, + l2Deployment, + rollupAddresses ) - const usingFeeToken = await _isUsingFeeToken(l1.inbox, l1Provider) - if (!usingFeeToken) + const usingFeeToken = await _isUsingFeeToken( + rollupAddresses.inbox, + l1Provider + ) + if (!usingFeeToken) { await checkL1WethGatewayInitialization( - L1WethGateway__factory.connect(l1.wethGateway, l1Provider), - l1, - l2 + L1WethGateway__factory.connect(l1Deployment.wethGateway, l1Provider), + l1Deployment, + l2Deployment, + rollupAddresses ) + } else { + expect(l1Deployment.wethGateway).to.be.eq(ethers.constants.AddressZero) + expect(l1Deployment.weth).to.be.eq(ethers.constants.AddressZero) + expect(l2Deployment.wethGateway).to.be.eq(ethers.constants.AddressZero) + expect(l2Deployment.weth).to.be.eq(ethers.constants.AddressZero) + } //// L2 checks await checkL2RouterInitialization( - L2GatewayRouter__factory.connect(l2.router, l2Provider), - l1, - l2 + L2GatewayRouter__factory.connect(l2Deployment.router, l2Provider), + l1Deployment, + l2Deployment ) await checkL2StandardGatewayInitialization( - L2ERC20Gateway__factory.connect(l2.standardGateway, l2Provider), - l1, - l2 + L2ERC20Gateway__factory.connect(l2Deployment.standardGateway, l2Provider), + l1Deployment, + l2Deployment ) await checkL2CustomGatewayInitialization( - L2CustomGateway__factory.connect(l2.customGateway, l2Provider), - l1, - l2 + L2CustomGateway__factory.connect(l2Deployment.customGateway, l2Provider), + l1Deployment, + l2Deployment ) await checkL2MulticallInitialization( - ArbMulticall2__factory.connect(l2.multicall, l2Provider) + ArbMulticall2__factory.connect(l2Deployment.multicall, l2Provider) ) if (!usingFeeToken) { await checkL2WethGatewayInitialization( - L2WethGateway__factory.connect(l2.wethGateway, l2Provider), - l1, - l2 + L2WethGateway__factory.connect(l2Deployment.wethGateway, l2Provider), + l1Deployment, + l2Deployment ) } const upgExecutor = new ethers.Contract( - l2.upgradeExecutor, + l2Deployment.upgradeExecutor, UpgradeExecutorABI, l2Provider ) - await checkL2UpgradeExecutorInitialization(upgExecutor, l1) + await checkL2UpgradeExecutorInitialization(upgExecutor, rollupAddresses) - await checkL1Ownership(l1) - await checkL2Ownership(l2) - await checkLogicContracts(usingFeeToken, l2) + await checkL1Ownership(l1Deployment, rollupAddresses) + await checkL2Ownership(l2Deployment, usingFeeToken) + await checkLogicContracts(usingFeeToken, l2Deployment) }) }) @@ -169,48 +181,42 @@ describe('tokenBridge', () => { async function checkL1RouterInitialization( l1Router: L1GatewayRouter, - l1: L1, - l2: L2, - creator: L1AtomicTokenBridgeCreator + l1Deployment: L1DeploymentAddresses, + l2Deployment: L2DeploymentAddresses, + rollupAddresses: RollupAddresses ) { console.log('checkL1RouterInitialization') - expect(l1.router.toLowerCase()).to.be.eq( - (await creator.getCanonicalL1RouterAddress(l1.inbox)).toLowerCase() - ) - expect((await l1Router.defaultGateway()).toLowerCase()).to.be.eq( - l1.standardGateway.toLowerCase() + l1Deployment.standardGateway.toLowerCase() ) expect((await l1Router.inbox()).toLowerCase()).to.be.eq( - l1.inbox.toLowerCase() + rollupAddresses.inbox.toLowerCase() ) expect((await l1Router.router()).toLowerCase()).to.be.eq( ethers.constants.AddressZero ) expect((await l1Router.counterpartGateway()).toLowerCase()).to.be.eq( - l2.router.toLowerCase() + l2Deployment.router.toLowerCase() ) } async function checkL1StandardGatewayInitialization( l1ERC20Gateway: L1ERC20Gateway, - l1: L1, - l2: L2 + l1Deployment: L1DeploymentAddresses, + l2Deployment: L2DeploymentAddresses, + rollupAddresses: RollupAddresses ) { console.log('checkL1StandardGatewayInitialization') expect((await l1ERC20Gateway.counterpartGateway()).toLowerCase()).to.be.eq( - l2.standardGateway.toLowerCase() + l2Deployment.standardGateway.toLowerCase() ) expect((await l1ERC20Gateway.router()).toLowerCase()).to.be.eq( - l1.router.toLowerCase() + l1Deployment.router.toLowerCase() ) expect((await l1ERC20Gateway.inbox()).toLowerCase()).to.be.eq( - l1.inbox.toLowerCase() - ) - expect((await l1ERC20Gateway.l2BeaconProxyFactory()).toLowerCase()).to.be.eq( - l2.beaconProxyFactory + rollupAddresses.inbox.toLowerCase() ) expect((await l1ERC20Gateway.l2BeaconProxyFactory()).toLowerCase()).to.be.eq( ( @@ -235,21 +241,22 @@ async function checkL1StandardGatewayInitialization( async function checkL1CustomGatewayInitialization( l1CustomGateway: L1CustomGateway, - l1: L1, - l2: L2 + l1Deployment: L1DeploymentAddresses, + l2Deployment: L2DeploymentAddresses, + rollupAddresses: RollupAddresses ) { console.log('checkL1CustomGatewayInitialization') expect((await l1CustomGateway.counterpartGateway()).toLowerCase()).to.be.eq( - l2.customGateway.toLowerCase() + l2Deployment.customGateway.toLowerCase() ) expect((await l1CustomGateway.router()).toLowerCase()).to.be.eq( - l1.router.toLowerCase() + l1Deployment.router.toLowerCase() ) expect((await l1CustomGateway.inbox()).toLowerCase()).to.be.eq( - l1.inbox.toLowerCase() + rollupAddresses.inbox.toLowerCase() ) expect((await l1CustomGateway.whitelist()).toLowerCase()).to.be.eq( @@ -259,21 +266,22 @@ async function checkL1CustomGatewayInitialization( async function checkL1WethGatewayInitialization( l1WethGateway: L1WethGateway, - l1: L1, - l2: L2 + l1Deployment: L1DeploymentAddresses, + l2Deployment: L2DeploymentAddresses, + rollupAddresses: RollupAddresses ) { console.log('checkL1WethGatewayInitialization') expect((await l1WethGateway.counterpartGateway()).toLowerCase()).to.be.eq( - l2.wethGateway.toLowerCase() + l2Deployment.wethGateway.toLowerCase() ) expect((await l1WethGateway.router()).toLowerCase()).to.be.eq( - l1.router.toLowerCase() + l1Deployment.router.toLowerCase() ) expect((await l1WethGateway.inbox()).toLowerCase()).to.be.eq( - l1.inbox.toLowerCase() + rollupAddresses.inbox.toLowerCase() ) expect((await l1WethGateway.l1Weth()).toLowerCase()).to.not.be.eq( @@ -287,7 +295,7 @@ async function checkL1WethGatewayInitialization( async function checkL2UpgradeExecutorInitialization( l2Executor: Contract, - l1: L1 + rollupAddresses: RollupAddresses ) { console.log('checkL2UpgradeExecutorInitialization') @@ -296,8 +304,9 @@ async function checkL2UpgradeExecutorInitialization( const executorRole = await l2Executor.EXECUTOR_ROLE() expect(await l2Executor.hasRole(adminRole, l2Executor.address)).to.be.true - expect(await l2Executor.hasRole(executorRole, l1.rollupOwner)).to.be.true - const aliasedL1Executor = applyAlias(l1.upgradeExecutor) + expect(await l2Executor.hasRole(executorRole, rollupAddresses.rollupOwner)).to + .be.true + const aliasedL1Executor = applyAlias(rollupAddresses.upgradeExecutor) expect(await l2Executor.hasRole(executorRole, aliasedL1Executor)).to.be.true } @@ -305,13 +314,13 @@ async function checkL2UpgradeExecutorInitialization( async function checkL2RouterInitialization( l2Router: L2GatewayRouter, - l1: L1, - l2: L2 + l1Deployment: L1DeploymentAddresses, + l2Deployment: L2DeploymentAddresses ) { console.log('checkL2RouterInitialization') expect((await l2Router.defaultGateway()).toLowerCase()).to.be.eq( - l2.standardGateway.toLowerCase() + l2Deployment.standardGateway.toLowerCase() ) expect((await l2Router.router()).toLowerCase()).to.be.eq( @@ -319,23 +328,23 @@ async function checkL2RouterInitialization( ) expect((await l2Router.counterpartGateway()).toLowerCase()).to.be.eq( - l1.router.toLowerCase() + l1Deployment.router.toLowerCase() ) } async function checkL2StandardGatewayInitialization( l2ERC20Gateway: L2ERC20Gateway, - l1: L1, - l2: L2 + l1Deployment: L1DeploymentAddresses, + l2Deployment: L2DeploymentAddresses ) { console.log('checkL2StandardGatewayInitialization') expect((await l2ERC20Gateway.counterpartGateway()).toLowerCase()).to.be.eq( - l1.standardGateway.toLowerCase() + l1Deployment.standardGateway.toLowerCase() ) expect((await l2ERC20Gateway.router()).toLowerCase()).to.be.eq( - l2.router.toLowerCase() + l2Deployment.router.toLowerCase() ) expect((await l2ERC20Gateway.beaconProxyFactory()).toLowerCase()).to.be.eq( @@ -359,33 +368,33 @@ async function checkL2StandardGatewayInitialization( async function checkL2CustomGatewayInitialization( l2CustomGateway: L2CustomGateway, - l1: L1, - l2: L2 + l1Deployment: L1DeploymentAddresses, + l2Deployment: L2DeploymentAddresses ) { console.log('checkL2CustomGatewayInitialization') expect((await l2CustomGateway.counterpartGateway()).toLowerCase()).to.be.eq( - l1.customGateway.toLowerCase() + l1Deployment.customGateway.toLowerCase() ) expect((await l2CustomGateway.router()).toLowerCase()).to.be.eq( - l2.router.toLowerCase() + l2Deployment.router.toLowerCase() ) } async function checkL2WethGatewayInitialization( l2WethGateway: L2WethGateway, - l1: L1, - l2: L2 + l1Deployment: L1DeploymentAddresses, + l2Deployment: L2DeploymentAddresses ) { console.log('checkL2WethGatewayInitialization') expect((await l2WethGateway.counterpartGateway()).toLowerCase()).to.be.eq( - l1.wethGateway.toLowerCase() + l1Deployment.wethGateway.toLowerCase() ) expect((await l2WethGateway.router()).toLowerCase()).to.be.eq( - l2.router.toLowerCase() + l2Deployment.router.toLowerCase() ) expect((await l2WethGateway.l1Weth()).toLowerCase()).to.not.be.eq( @@ -403,75 +412,91 @@ async function checkL2MulticallInitialization(l2Multicall: ArbMulticall2) { expect(l2MulticallCode.length).to.be.gt(0) } -async function checkL1Ownership(l1: L1) { +async function checkL1Ownership( + l1Deployment: L1DeploymentAddresses, + rollupAddresses: RollupAddresses +) { console.log('checkL1Ownership') // check proxyAdmins - expect(await _getProxyAdmin(l1.router, l1Provider)).to.be.eq(l1.proxyAdmin) - expect(await _getProxyAdmin(l1.standardGateway, l1Provider)).to.be.eq( - l1.proxyAdmin - ) - expect(await _getProxyAdmin(l1.customGateway, l1Provider)).to.be.eq( - l1.proxyAdmin + expect(await _getProxyAdmin(l1Deployment.router, l1Provider)).to.be.eq( + rollupAddresses.proxyAdmin ) - if (l1.wethGateway !== ethers.constants.AddressZero) { - expect(await _getProxyAdmin(l1.wethGateway, l1Provider)).to.be.eq( - l1.proxyAdmin + expect( + await _getProxyAdmin(l1Deployment.standardGateway, l1Provider) + ).to.be.eq(rollupAddresses.proxyAdmin) + expect(await _getProxyAdmin(l1Deployment.customGateway, l1Provider)).to.be.eq( + rollupAddresses.proxyAdmin + ) + if (l1Deployment.wethGateway !== ethers.constants.AddressZero) { + expect(await _getProxyAdmin(l1Deployment.wethGateway, l1Provider)).to.be.eq( + rollupAddresses.proxyAdmin ) } - expect(await _getProxyAdmin(l1.upgradeExecutor, l1Provider)).to.be.eq( - l1.proxyAdmin - ) + expect( + await _getProxyAdmin(rollupAddresses.upgradeExecutor, l1Provider) + ).to.be.eq(rollupAddresses.proxyAdmin) // check ownables - expect(await _getOwner(l1.proxyAdmin, l1Provider)).to.be.eq( - l1.upgradeExecutor + expect(await _getOwner(rollupAddresses.proxyAdmin, l1Provider)).to.be.eq( + rollupAddresses.upgradeExecutor ) - expect(await _getOwner(l1.router, l1Provider)).to.be.eq(l1.upgradeExecutor) - expect(await _getOwner(l1.customGateway, l1Provider)).to.be.eq( - l1.upgradeExecutor + expect(await _getOwner(l1Deployment.router, l1Provider)).to.be.eq( + rollupAddresses.upgradeExecutor + ) + expect(await _getOwner(l1Deployment.customGateway, l1Provider)).to.be.eq( + rollupAddresses.upgradeExecutor ) } -async function checkL2Ownership(l2: L2) { +async function checkL2Ownership( + l2Deployment: L2DeploymentAddresses, + usingFeeToken: boolean +) { console.log('checkL2Ownership') - const l2ProxyAdmin = await _getProxyAdmin(l2.router, l2Provider) + const l2ProxyAdmin = await _getProxyAdmin(l2Deployment.router, l2Provider) // check proxyAdmins - expect(l2ProxyAdmin).to.be.eq(l2.proxyAdmin) - expect(await _getProxyAdmin(l2.router, l2Provider)).to.be.eq(l2ProxyAdmin) - expect(await _getProxyAdmin(l2.standardGateway, l2Provider)).to.be.eq( + expect(await _getProxyAdmin(l2Deployment.router, l2Provider)).to.be.eq( l2ProxyAdmin ) - expect(await _getProxyAdmin(l2.customGateway, l2Provider)).to.be.eq( + expect( + await _getProxyAdmin(l2Deployment.standardGateway, l2Provider) + ).to.be.eq(l2ProxyAdmin) + expect(await _getProxyAdmin(l2Deployment.customGateway, l2Provider)).to.be.eq( l2ProxyAdmin ) - if (l2.wethGateway != ethers.constants.AddressZero) { - expect(await _getProxyAdmin(l2.wethGateway, l2Provider)).to.be.eq( + if (!usingFeeToken) { + expect(await _getProxyAdmin(l2Deployment.wethGateway, l2Provider)).to.be.eq( l2ProxyAdmin ) } - expect(await _getProxyAdmin(l2.upgradeExecutor, l2Provider)).to.be.eq( - l2ProxyAdmin - ) + expect( + await _getProxyAdmin(l2Deployment.upgradeExecutor, l2Provider) + ).to.be.eq(l2ProxyAdmin) // check ownables - expect(await _getOwner(l2ProxyAdmin, l2Provider)).to.be.eq(l2.upgradeExecutor) + expect(await _getOwner(l2ProxyAdmin, l2Provider)).to.be.eq( + l2Deployment.upgradeExecutor.toLowerCase() + ) } -async function checkLogicContracts(usingFeeToken: boolean, l2: L2) { +async function checkLogicContracts( + usingFeeToken: boolean, + l2Deployment: L2DeploymentAddresses +) { console.log('checkLogicContracts') const upgExecutorLogic = await _getLogicAddress( - l2.upgradeExecutor, + l2Deployment.upgradeExecutor, l2Provider ) expect(await _isInitialized(upgExecutorLogic, l2Provider)).to.be.true - const routerLogic = await _getLogicAddress(l2.router, l2Provider) + const routerLogic = await _getLogicAddress(l2Deployment.router, l2Provider) expect( await L2GatewayRouter__factory.connect( routerLogic, @@ -480,7 +505,7 @@ async function checkLogicContracts(usingFeeToken: boolean, l2: L2) { ).to.be.not.eq(ethers.constants.AddressZero) const standardGatewayLogic = await _getLogicAddress( - l2.standardGateway, + l2Deployment.standardGateway, l2Provider ) expect( @@ -491,7 +516,7 @@ async function checkLogicContracts(usingFeeToken: boolean, l2: L2) { ).to.be.not.eq(ethers.constants.AddressZero) const customGatewayLogic = await _getLogicAddress( - l2.customGateway, + l2Deployment.customGateway, l2Provider ) expect( @@ -502,7 +527,10 @@ async function checkLogicContracts(usingFeeToken: boolean, l2: L2) { ).to.be.not.eq(ethers.constants.AddressZero) if (!usingFeeToken) { - const wethGatewayLogic = await _getLogicAddress(l2.wethGateway, l2Provider) + const wethGatewayLogic = await _getLogicAddress( + l2Deployment.wethGateway, + l2Provider + ) expect( await L2WethGateway__factory.connect( wethGatewayLogic, @@ -510,7 +538,7 @@ async function checkLogicContracts(usingFeeToken: boolean, l2: L2) { ).counterpartGateway() ).to.be.not.eq(ethers.constants.AddressZero) - const wethLogic = await _getLogicAddress(l2.weth, l2Provider) + const wethLogic = await _getLogicAddress(l2Deployment.weth, l2Provider) expect( await AeWETH__factory.connect(wethLogic, l2Provider).l2Gateway() ).to.be.not.eq(ethers.constants.AddressZero) @@ -530,118 +558,50 @@ async function _isUsingFeeToken(inbox: string, l1Provider: JsonRpcProvider) { return true } -async function _getTokenBridgeAddresses( +async function _getAddresses( rollupAddress: string, l1TokenBridgeCreatorAddress: string ) { - const inboxAddress = await RollupCore__factory.connect( - rollupAddress, - l1Provider - ).inbox() - const l1TokenBridgeCreator = L1AtomicTokenBridgeCreator__factory.connect( l1TokenBridgeCreatorAddress, l1Provider ) - //// L1 - // find all the events emitted by this address - - const filter: Filter = { - address: l1TokenBridgeCreatorAddress, - topics: [ - ethers.utils.id( - 'OrbitTokenBridgeCreated(address,address,address,address,address,address,address,address)' - ), - ethers.utils.hexZeroPad(inboxAddress, 32), - ], - } - - const currentBlock = await l1Provider.getBlockNumber() - const fromBlock = currentBlock - 100000 // ~last 24h on - const logs = await l1Provider.getLogs({ - ...filter, - fromBlock: fromBlock, - toBlock: 'latest', - }) + /// get core contracts addresses + const inbox = await RollupCore__factory.connect( + rollupAddress, + l1Provider + ).inbox() - if (logs.length === 0) { - throw new Error( - "Couldn't find any OrbitTokenBridgeCreated events in block range[" + - fromBlock + - ',latest]' - ) - } + const multicall = await l1TokenBridgeCreator.l1Multicall() + const proxyAdmin = await IInboxProxyAdmin__factory.connect( + inbox, + l1Provider + ).getProxyAdmin() - const logData = l1TokenBridgeCreator.interface.parseLog(logs[0]) + const upgradeExecutor = await IOwnable__factory.connect( + rollupAddress, + l1Provider + ).owner() - const { - inbox, - owner, - router, - standardGateway, - customGateway, - wethGateway, - proxyAdmin, - upgradeExecutor, - } = logData.args - const l1 = { + const rollupAddresses = { + rollup: rollupAddress.toLowerCase(), inbox: inbox.toLowerCase(), - rollupOwner: owner.toLowerCase(), - router: router.toLowerCase(), - standardGateway: standardGateway.toLowerCase(), - customGateway: customGateway.toLowerCase(), - wethGateway: wethGateway.toLowerCase(), + rollupOwner: await _getRollupOwnerFromLogs( + l1Provider, + l1TokenBridgeCreator, + inbox + ), proxyAdmin: proxyAdmin.toLowerCase(), upgradeExecutor: upgradeExecutor.toLowerCase(), + multicall: multicall.toLowerCase(), } - const usingFeeToken = await _isUsingFeeToken(l1.inbox, l1Provider) - - const chainId = await IRollupCore__factory.connect( - rollupAddress, - l1Provider - ).chainId() - - //// L2 - const l2 = { - router: ( - await l1TokenBridgeCreator.getCanonicalL2RouterAddress(chainId) - ).toLowerCase(), - standardGateway: ( - await l1TokenBridgeCreator.getCanonicalL2StandardGatewayAddress(chainId) - ).toLowerCase(), - customGateway: ( - await l1TokenBridgeCreator.getCanonicalL2CustomGatewayAddress(chainId) - ).toLowerCase(), - wethGateway: (usingFeeToken - ? ethers.constants.AddressZero - : await l1TokenBridgeCreator.getCanonicalL2WethGatewayAddress(chainId) - ).toLowerCase(), - weth: (usingFeeToken - ? ethers.constants.AddressZero - : await l1TokenBridgeCreator.getCanonicalL2WethAddress(chainId) - ).toLowerCase(), - upgradeExecutor: ( - await l1TokenBridgeCreator.getCanonicalL2UpgradeExecutorAddress(chainId) - ).toLowerCase(), - multicall: ( - await l1TokenBridgeCreator.getCanonicalL2Multicall(chainId) - ).toLowerCase(), - proxyAdmin: ( - await l1TokenBridgeCreator.getCanonicalL2ProxyAdminAddress(chainId) - ).toLowerCase(), - beaconProxyFactory: ( - await l1TokenBridgeCreator.getCanonicalL2BeaconProxyFactoryAddress( - chainId - ) - ).toLowerCase(), - } + /// fetch deployment addresses from registry + const l1Deployment = await l1TokenBridgeCreator.inboxToL1Deployment(inbox) + const l2Deployment = await l1TokenBridgeCreator.inboxToL2Deployment(inbox) - return { - l1, - l2, - } + return { rollupAddresses, l1Deployment, l2Deployment } } async function _getProxyAdmin( @@ -701,6 +661,37 @@ async function _getAddressAtStorageSlot( return ethers.utils.getAddress(formatAddress) } +async function _getRollupOwnerFromLogs( + provider: JsonRpcProvider, + l1TokenBridgeCreator: L1AtomicTokenBridgeCreator, + inboxAddress: string +): Promise { + const filter: Filter = { + address: l1TokenBridgeCreator.address, + topics: [ + ethers.utils.id( + 'OrbitTokenBridgeCreated(address,address,(address,address,address,address,address),(address,address,address,address,address,address,address,address,address),address,address)' + ), + ethers.utils.hexZeroPad(inboxAddress, 32), + ], + } + + // Fetch the logs + const logs = await provider.getLogs({ + ...filter, + fromBlock: '0x1', + toBlock: 'latest', + }) + if (logs.length === 0) { + throw new Error( + `Couldn't find any OrbitTokenBridgeCreated events for inbox ${inboxAddress}` + ) + } + + const logData = l1TokenBridgeCreator.interface.parseLog(logs[logs.length - 1]) + return logData.args.owner +} + /** * Return if contracts is initialized or not. Applicable for contracts which use OpenZeppelin Initializable pattern, * so state of initialization is stored as uint8 in storage slot 0, offset 0. @@ -718,25 +709,30 @@ async function _isInitialized( return maskedValue.toNumber() == 1 } -interface L1 { +interface RollupAddresses { + rollup: string inbox: string rollupOwner: string + proxyAdmin: string + upgradeExecutor: string + multicall: string +} + +interface L1DeploymentAddresses { router: string standardGateway: string customGateway: string wethGateway: string - proxyAdmin: string - upgradeExecutor: string + weth: string } - -interface L2 { +interface L2DeploymentAddresses { router: string standardGateway: string customGateway: string wethGateway: string weth: string - upgradeExecutor: string - multicall: string proxyAdmin: string beaconProxyFactory: string + upgradeExecutor: string + multicall: string } From f9e1f64d6e9aa3158719c1e25218c3175c2d4ff7 Mon Sep 17 00:00:00 2001 From: gzeon Date: Tue, 19 Dec 2023 16:42:12 +0800 Subject: [PATCH 28/48] chore: update deployment scripts Co-authored-by: Goran Vladika --- scripts/atomicTokenBridgeDeployer.ts | 81 +++---------------- scripts/deployment/createTokenBridge.ts | 45 ++++++----- .../deployCreatorAndCreateTokenBridge.ts | 46 ++++++----- 3 files changed, 61 insertions(+), 111 deletions(-) diff --git a/scripts/atomicTokenBridgeDeployer.ts b/scripts/atomicTokenBridgeDeployer.ts index 2eacc47490..cadbee9492 100644 --- a/scripts/atomicTokenBridgeDeployer.ts +++ b/scripts/atomicTokenBridgeDeployer.ts @@ -25,6 +25,7 @@ import { IRollupCore__factory, IBridge__factory, Multicall2__factory, + IInboxProxyAdmin__factory, } from '../build/types' import { abi as UpgradeExecutorABI, @@ -177,73 +178,18 @@ export const createTokenBridge = async ( ) console.log('L2AtomicTokenBridgeFactory', l2AtomicTokenBridgeFactory.address) - /// pick up L1 contracts from events - const { - router: l1Router, - standardGateway: l1StandardGateway, - customGateway: l1CustomGateway, - wethGateway: l1WethGateway, - proxyAdmin: l1ProxyAdmin, - } = getParsedLogs( - receipt.logs, - l1TokenBridgeCreator.interface, - 'OrbitTokenBridgeCreated' - )[0].args - - const rollup = await IBridge__factory.connect( - await IInbox__factory.connect(inbox, l1Signer).bridge(), - l1Signer - ).rollup() - const chainId = await IRollupCore__factory.connect(rollup, l1Signer).chainId() + /// fetch deployment addresses from registry + const l1Deployment = await l1TokenBridgeCreator.inboxToL1Deployment(inbox) + const l2Deployment = await l1TokenBridgeCreator.inboxToL2Deployment(inbox) - /// pick up L2 contracts - const l2Router = await l1TokenBridgeCreator.getCanonicalL2RouterAddress( - chainId - ) - const l2StandardGateway = L2ERC20Gateway__factory.connect( - await l1TokenBridgeCreator.getCanonicalL2StandardGatewayAddress(chainId), - l2Provider - ) - const beaconProxyFactory = await l2StandardGateway.beaconProxyFactory() - const l2CustomGateway = - await l1TokenBridgeCreator.getCanonicalL2CustomGatewayAddress(chainId) - - const isUsingFeeToken = feeToken != ethers.constants.AddressZero - const l2WethGateway = isUsingFeeToken - ? ethers.constants.AddressZero - : L2WethGateway__factory.connect( - await l1TokenBridgeCreator.getCanonicalL2WethGatewayAddress(chainId), - l2Provider - ).address - const l1Weth = await l1TokenBridgeCreator.l1Weth() - const l2Weth = isUsingFeeToken - ? ethers.constants.AddressZero - : await l1TokenBridgeCreator.getCanonicalL2WethAddress(chainId) - const l2ProxyAdmin = - await l1TokenBridgeCreator.getCanonicalL2ProxyAdminAddress(chainId) - - const l1Multicall = await l1TokenBridgeCreator.l1Multicall() - const l2Multicall = await l1TokenBridgeCreator.getCanonicalL2Multicall( - chainId - ) + /// fetch l1 multicall and l1 proxy admin from creator + const l1MultiCall = await l1TokenBridgeCreator.l1Multicall() + const l1ProxyAdmin = await IInboxProxyAdmin__factory.connect( + inbox, + l1Signer.provider! + ).getProxyAdmin() - return { - l1Router, - l1StandardGateway, - l1CustomGateway, - l1WethGateway, - l1ProxyAdmin, - l1Multicall, - l2Router, - l2StandardGateway: l2StandardGateway.address, - l2CustomGateway, - l2WethGateway, - l1Weth, - l2Weth, - beaconProxyFactory, - l2ProxyAdmin, - l2Multicall, - } + return { l1Deployment, l2Deployment, l1MultiCall, l1ProxyAdmin } } /** @@ -271,9 +217,7 @@ export const deployL1TokenBridgeCreator = async ( await l1TokenBridgeCreatorProxyAdmin.deployed() const l1TokenBridgeCreatorLogic = - await new L1AtomicTokenBridgeCreator__factory(l1Deployer).deploy( - l2MulticallAddressOnL1.address - ) + await new L1AtomicTokenBridgeCreator__factory(l1Deployer).deploy() await l1TokenBridgeCreatorLogic.deployed() const l1TokenBridgeCreatorProxy = @@ -489,6 +433,7 @@ export const deployL1TokenBridgeCreator = async ( l2CustomGatewayAddressOnL1.address, l2WethGatewayAddressOnL1.address, l2WethAddressOnL1.address, + l2MulticallAddressOnL1.address, l1WethAddress, l1Multicall.address, gasLimitForL2FactoryDeployment diff --git a/scripts/deployment/createTokenBridge.ts b/scripts/deployment/createTokenBridge.ts index 5cf65f9ee9..0e30a8cf73 100644 --- a/scripts/deployment/createTokenBridge.ts +++ b/scripts/deployment/createTokenBridge.ts @@ -63,32 +63,33 @@ export const createTokenBridgeOnTargetChain = async () => { ) // create token bridge - const deployedContracts = await createTokenBridge( - l1Deployer, - l2Provider, - l1TokenBridgeCreator, - envVars.rollupAddress, - envVars.rollupOwner - ) + const { l1Deployment, l2Deployment, l1MultiCall, l1ProxyAdmin } = + await createTokenBridge( + l1Deployer, + l2Provider, + l1TokenBridgeCreator, + envVars.rollupAddress, + envVars.rollupOwner + ) const l2Network = { ...corel2Network, tokenBridge: { - l1CustomGateway: deployedContracts.l1CustomGateway, - l1ERC20Gateway: deployedContracts.l1StandardGateway, - l1GatewayRouter: deployedContracts.l1Router, - l1MultiCall: deployedContracts.l1Multicall, - l1ProxyAdmin: deployedContracts.l1ProxyAdmin, - l1Weth: deployedContracts.l1Weth, - l1WethGateway: deployedContracts.l1WethGateway, - - l2CustomGateway: deployedContracts.l2CustomGateway, - l2ERC20Gateway: deployedContracts.l2StandardGateway, - l2GatewayRouter: deployedContracts.l2Router, - l2Multicall: deployedContracts.l2Multicall, - l2ProxyAdmin: deployedContracts.l2ProxyAdmin, - l2Weth: deployedContracts.l2Weth, - l2WethGateway: deployedContracts.l2WethGateway, + l1CustomGateway: l1Deployment.customGateway, + l1ERC20Gateway: l1Deployment.standardGateway, + l1GatewayRouter: l1Deployment.router, + l1MultiCall: l1MultiCall, + l1ProxyAdmin: l1ProxyAdmin, + l1Weth: l1Deployment.weth, + l1WethGateway: l1Deployment.wethGateway, + + l2CustomGateway: l2Deployment.customGateway, + l2ERC20Gateway: l2Deployment.standardGateway, + l2GatewayRouter: l2Deployment.router, + l2Multicall: l2Deployment.multicall, + l2ProxyAdmin: l2Deployment.proxyAdmin, + l2Weth: l2Deployment.weth, + l2WethGateway: l2Deployment.wethGateway, }, } diff --git a/scripts/local-deployment/deployCreatorAndCreateTokenBridge.ts b/scripts/local-deployment/deployCreatorAndCreateTokenBridge.ts index f1c7402d83..d4597a26f9 100644 --- a/scripts/local-deployment/deployCreatorAndCreateTokenBridge.ts +++ b/scripts/local-deployment/deployCreatorAndCreateTokenBridge.ts @@ -125,33 +125,37 @@ export const setupTokenBridgeInLocalEnv = async () => { console.log('L1TokenBridgeRetryableSender', retryableSender.address) // create token bridge - console.log('Creating token bridge') - const deployedContracts = await createTokenBridge( - parentDeployer, - childDeployer.provider!, - l1TokenBridgeCreator, - coreL2Network.ethBridge.rollup, - rollupOwner + console.log( + '\nCreating token bridge for rollup', + coreL2Network.ethBridge.rollup ) + const { l1Deployment, l2Deployment, l1MultiCall, l1ProxyAdmin } = + await createTokenBridge( + parentDeployer, + childDeployer.provider!, + l1TokenBridgeCreator, + coreL2Network.ethBridge.rollup, + rollupOwner + ) const l2Network: L2Network = { ...coreL2Network, tokenBridge: { - l1CustomGateway: deployedContracts.l1CustomGateway, - l1ERC20Gateway: deployedContracts.l1StandardGateway, - l1GatewayRouter: deployedContracts.l1Router, - l1MultiCall: deployedContracts.l1Multicall, - l1ProxyAdmin: deployedContracts.l1ProxyAdmin, - l1Weth: deployedContracts.l1Weth, - l1WethGateway: deployedContracts.l1WethGateway, + l1CustomGateway: l1Deployment.customGateway, + l1ERC20Gateway: l1Deployment.standardGateway, + l1GatewayRouter: l1Deployment.router, + l1MultiCall: l1MultiCall, + l1ProxyAdmin: l1ProxyAdmin, + l1Weth: l1Deployment.weth, + l1WethGateway: l1Deployment.wethGateway, - l2CustomGateway: deployedContracts.l2CustomGateway, - l2ERC20Gateway: deployedContracts.l2StandardGateway, - l2GatewayRouter: deployedContracts.l2Router, - l2Multicall: deployedContracts.l2Multicall, - l2ProxyAdmin: deployedContracts.l2ProxyAdmin, - l2Weth: deployedContracts.l2Weth, - l2WethGateway: deployedContracts.l2WethGateway, + l2CustomGateway: l2Deployment.customGateway, + l2ERC20Gateway: l2Deployment.standardGateway, + l2GatewayRouter: l2Deployment.router, + l2Multicall: l2Deployment.multicall, + l2ProxyAdmin: l2Deployment.proxyAdmin, + l2Weth: l2Deployment.weth, + l2WethGateway: l2Deployment.wethGateway, }, } From d5dc5c234981942bb35afa71004de35a43a5b9f6 Mon Sep 17 00:00:00 2001 From: gzeon Date: Tue, 12 Dec 2023 18:27:50 +0800 Subject: [PATCH 29/48] fix: unsalted deployment --- .../arbitrum/L2AtomicTokenBridgeFactory.sol | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol b/contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol index 5781afff60..2b09c40b44 100644 --- a/contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol +++ b/contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol @@ -96,7 +96,9 @@ contract L2AtomicTokenBridgeFactory { // Create UpgradeExecutor logic and upgrade to it. address upExecutorLogic = Create2.deploy( - 0, OrbitSalts.UNSALTED, CreationCodeHelper.getCreationCodeFor(runtimeCode) + 0, + _getL2Salt(OrbitSalts.L2_EXECUTOR), + CreationCodeHelper.getCreationCodeFor(runtimeCode) ); ProxyAdmin(proxyAdmin).upgrade( @@ -127,7 +129,7 @@ contract L2AtomicTokenBridgeFactory { // create L2 router logic and upgrade address routerLogic = Create2.deploy( - 0, OrbitSalts.UNSALTED, CreationCodeHelper.getCreationCodeFor(runtimeCode) + 0, _getL2Salt(OrbitSalts.L2_ROUTER), CreationCodeHelper.getCreationCodeFor(runtimeCode) ); ProxyAdmin(proxyAdmin).upgrade(ITransparentUpgradeableProxy(canonicalRouter), routerLogic); @@ -152,7 +154,9 @@ contract L2AtomicTokenBridgeFactory { // create L2 standard gateway logic and upgrade address stdGatewayLogic = Create2.deploy( - 0, OrbitSalts.UNSALTED, CreationCodeHelper.getCreationCodeFor(runtimeCode) + 0, + _getL2Salt(OrbitSalts.L2_STANDARD_GATEWAY), + CreationCodeHelper.getCreationCodeFor(runtimeCode) ); ProxyAdmin(proxyAdmin).upgrade( ITransparentUpgradeableProxy(canonicalStdGateway), stdGatewayLogic @@ -162,9 +166,11 @@ contract L2AtomicTokenBridgeFactory { L2ERC20Gateway(stdGatewayLogic).initialize(ADDRESS_DEAD, ADDRESS_DEAD, ADDRESS_DEAD); // create beacon - StandardArbERC20 standardArbERC20 = new StandardArbERC20{salt: OrbitSalts.UNSALTED}(); - UpgradeableBeacon beacon = - new UpgradeableBeacon{salt: OrbitSalts.UNSALTED}(address(standardArbERC20)); + StandardArbERC20 standardArbERC20 = + new StandardArbERC20{salt: _getL2Salt(OrbitSalts.BEACON_PROXY_FACTORY)}(); + UpgradeableBeacon beacon = new UpgradeableBeacon{ + salt: _getL2Salt(OrbitSalts.BEACON_PROXY_FACTORY) + }(address(standardArbERC20)); BeaconProxyFactory beaconProxyFactory = new BeaconProxyFactory{salt: _getL2Salt(OrbitSalts.BEACON_PROXY_FACTORY)}(); @@ -189,7 +195,9 @@ contract L2AtomicTokenBridgeFactory { // create L2 custom gateway logic and upgrade address customGatewayLogicAddress = Create2.deploy( - 0, OrbitSalts.UNSALTED, CreationCodeHelper.getCreationCodeFor(runtimeCode) + 0, + _getL2Salt(OrbitSalts.L2_CUSTOM_GATEWAY), + CreationCodeHelper.getCreationCodeFor(runtimeCode) ); ProxyAdmin(proxyAdmin).upgrade( ITransparentUpgradeableProxy(canonicalCustomGateway), customGatewayLogicAddress @@ -215,7 +223,9 @@ contract L2AtomicTokenBridgeFactory { // Create L2WETH logic and upgrade address l2WethLogic = Create2.deploy( - 0, OrbitSalts.UNSALTED, CreationCodeHelper.getCreationCodeFor(aeWethRuntimeCode) + 0, + _getL2Salt(OrbitSalts.L2_WETH), + CreationCodeHelper.getCreationCodeFor(aeWethRuntimeCode) ); ProxyAdmin(proxyAdmin).upgrade(ITransparentUpgradeableProxy(canonicalL2Weth), l2WethLogic); @@ -224,7 +234,9 @@ contract L2AtomicTokenBridgeFactory { // create L2WETH gateway logic and upgrade address l2WethGatewayLogic = Create2.deploy( - 0, OrbitSalts.UNSALTED, CreationCodeHelper.getCreationCodeFor(wethGatewayRuntimeCode) + 0, + _getL2Salt(OrbitSalts.L2_WETH_GATEWAY), + CreationCodeHelper.getCreationCodeFor(wethGatewayRuntimeCode) ); ProxyAdmin(proxyAdmin).upgrade( ITransparentUpgradeableProxy(canonicalL2WethGateway), l2WethGatewayLogic @@ -286,10 +298,9 @@ struct L2RuntimeCode { /** * Collection of salts used in CREATE2 deployment of L2 token bridge contracts. + * Logic contracts are deployed using the same salt as the proxy, it's fine as they have different code */ library OrbitSalts { - bytes32 internal constant UNSALTED = bytes32(0); - bytes internal constant L1_ROUTER = bytes("L1R"); bytes internal constant L1_STANDARD_GATEWAY = bytes("L1SGW"); bytes internal constant L1_CUSTOM_GATEWAY = bytes("L1CGW"); From e6ead0fd2736c4b358643872f1714ead5feec23b Mon Sep 17 00:00:00 2001 From: gzeon Date: Tue, 12 Dec 2023 20:30:56 +0800 Subject: [PATCH 30/48] test: AtomicTokenBridgeCreator in foundry --- test-foundry/AtomicTokenBridgeFactory.t.sol | 242 ++++++++++++++++++++ 1 file changed, 242 insertions(+) create mode 100644 test-foundry/AtomicTokenBridgeFactory.t.sol diff --git a/test-foundry/AtomicTokenBridgeFactory.t.sol b/test-foundry/AtomicTokenBridgeFactory.t.sol new file mode 100644 index 0000000000..c9b7e90664 --- /dev/null +++ b/test-foundry/AtomicTokenBridgeFactory.t.sol @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.8.0; + +import "forge-std/Test.sol"; + +import "../contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol"; +import "../contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol"; +import "../contracts/tokenbridge/libraries/AddressAliasHelper.sol"; + +import {L1TokenBridgeRetryableSender} from + "../contracts/tokenbridge/ethereum/L1TokenBridgeRetryableSender.sol"; +import {TestWETH9} from "../contracts/tokenbridge/test/TestWETH9.sol"; +import {Multicall2} from "../contracts/rpc-utils/MulticallV2.sol"; + +import {TransparentUpgradeableProxy} from + "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +// // Check that the rollupOwner account has EXECUTOR role +// // on the upgrade executor which is the owner of the rollup +// address upgradeExecutor = IInbox(inbox).bridge().rollup().owner(); +// if ( +// !IAccessControlUpgradeable(upgradeExecutor).hasRole( +// UpgradeExecutor(upgradeExecutor).EXECUTOR_ROLE(), rollupOwner +// ) +// ) { +// revert L1AtomicTokenBridgeCreator_RollupOwnershipMisconfig(); +// } + +/// @dev This inbox mock is used to bypass sanity checks in the L1AtomicTokenBridgeCreator +contract MockInbox is Test { + bytes32 public constant EXECUTOR_ROLE = keccak256("EXECUTOR_ROLE"); + address public constant nativeToken = address(0); + uint256 public immutable chainId; + uint256 public mode; + + constructor(uint256 _mode) { + chainId = block.chainid; + mode = _mode; + } + + function setMode(uint256 _mode) external { + mode = _mode; + } + + function bridge() external view returns (address) { + return address(this); + } + + function rollup() external view returns (address) { + return address(this); + } + + function owner() external view returns (address) { + return address(this); + } + + function hasRole(bytes32, address) external view returns (bool) { + return true; + } + + function getProxyAdmin() external view returns (address) { + return address(this); + } + + function calculateRetryableSubmissionFee(uint256, uint256) external view returns (uint256) { + return 0; + } + + function createRetryableTicket( + address to, + uint256 l2CallValue, + uint256, + address, + address, + uint256, + uint256 maxFeePerGas, + bytes memory data + ) external payable returns (uint256) { + if (mode == 1) { + // mode 1: frontrun the call + if (to != address(0)) { + (bool success,) = to.call{value: l2CallValue}(data); + if (!success) { + revert("frontrun failed"); + } + } + } + vm.startPrank(AddressAliasHelper.applyL1ToL2Alias(msg.sender)); + if (to == address(0)) { + address addr; + assembly { + addr := create(0, add(data, 0x20), mload(data)) + if iszero(extcodesize(addr)) { revert(0, 0) } + } + } else { + (bool success,) = to.call{value: l2CallValue}(data); + if (!success) { + revert(); + } + } + vm.stopPrank(); + } +} + +contract AtomicTokenBridgeCreatorTest is Test { + L1AtomicTokenBridgeCreator.L1Templates public l1Templates; + + address public l2TokenBridgeFactoryTemplate; + address public l2RouterTemplate; + address public l2StandardGatewayTemplate; + address public l2CustomGatewayTemplate; + address public l2WethGatewayTemplate; + address public l2WethTemplate; + address public l2MulticallTemplate; + + address public l1Weth; + address public l1MultiCall; + + L1AtomicTokenBridgeCreator public factory; + + uint256 public constant MAX_DEPLOYMENT_GAS = 30 * 1024 * 16; // 30 bytes, 16 gas per byte + address public constant PROXY_ADMIN = address(111); + + receive() external payable {} + + function setUp() public { + l1Templates = L1AtomicTokenBridgeCreator.L1Templates( + L1GatewayRouter(address(new L1GatewayRouter())), + L1ERC20Gateway(address(new L1ERC20Gateway())), + L1CustomGateway(address(new L1CustomGateway())), + L1WethGateway(payable(new L1WethGateway())), + L1OrbitGatewayRouter(address(new L1OrbitGatewayRouter())), + L1OrbitERC20Gateway(address(new L1OrbitERC20Gateway())), + L1OrbitCustomGateway(address(new L1OrbitCustomGateway())), + IUpgradeExecutor(address(new UpgradeExecutor())) + ); + l2TokenBridgeFactoryTemplate = address(new L2AtomicTokenBridgeFactory()); + l2RouterTemplate = address(new L2GatewayRouter()); + l2StandardGatewayTemplate = address(new L2ERC20Gateway()); + l2CustomGatewayTemplate = address(new L2CustomGateway()); + l2WethGatewayTemplate = address(new L2WethGateway()); + l2WethTemplate = address(new aeWETH()); + l2MulticallTemplate = address(new ArbMulticall2()); + + l1Weth = address(new TestWETH9("wethl1", "wl1")); + l1MultiCall = address(new Multicall2()); + + L1TokenBridgeRetryableSender sender = new L1TokenBridgeRetryableSender(); + address factorylogic = address(new L1AtomicTokenBridgeCreator()); + factory = L1AtomicTokenBridgeCreator( + address(new TransparentUpgradeableProxy(factorylogic, PROXY_ADMIN, "")) + ); + factory.initialize(sender); + factory.setTemplates( + l1Templates, + l2TokenBridgeFactoryTemplate, + l2RouterTemplate, + l2StandardGatewayTemplate, + l2CustomGatewayTemplate, + l2WethGatewayTemplate, + l2WethTemplate, + l2MulticallTemplate, + l1Weth, + l1MultiCall, + MAX_DEPLOYMENT_GAS + ); + } + + function testDeployment() public { + MockInbox inbox = new MockInbox(0); + _testDeployment(address(inbox)); + } + + function testDeploymentFrontrun() public { + MockInbox inbox = new MockInbox(1); + _testDeployment(address(inbox)); + } + + function _testDeployment(address inbox) internal { + factory.createTokenBridge({ + inbox: address(inbox), + rollupOwner: address(this), + maxGasForContracts: 0, + gasPriceBid: 0 + }); + { + address l2factory = factory.canonicalL2FactoryAddress(); + assertEq(l2factory, 0x20011A455c9eBBeD73CA307539D3e9Baff600fBD); + assertTrue(l2factory.code.length > 0); + } + + { + (address l1r, address l1sgw, address l1cgw, address l1wgw, address l1w) = + factory.inboxToL1Deployment(address(inbox)); + assertEq(l1r, 0xcB37BCa7042A10FfA75Ff95Ad8B361A13bbAA63A, "l1r"); + assertTrue(l1r.code.length > 0, "l1r code"); + assertEq(l1sgw, 0x013b54d88f76fb9D05b8382747beb1B4Df313507, "l1sgw"); + assertTrue(l1sgw.code.length > 0, "l1sgw code"); + assertEq(l1cgw, 0xf8663294698E0623de82B9791906454A2036575F, "l1cgw"); + assertTrue(l1cgw.code.length > 0, "l1cgw code"); + assertEq(l1wgw, 0x79eF26bE05C5643D5AdC81B8c7e49b0898A74428, "l1wgw"); + assertTrue(l1wgw.code.length > 0, "l1wgw code"); + assertEq(l1w, 0x96d3F6c20EEd2697647F543fE6C08bC2Fbf39758, "l1w"); + assertTrue(l1w.code.length > 0, "l1w code"); + } + { + ( + address l2r, + address l2sgw, + address l2cgw, + address l2wgw, + address l2w, + address l2pa, + address l2bpf, + address l2ue, + address l2mc + ) = factory.inboxToL2Deployment(address(inbox)); + + assertEq(l2r, 0xdB4050B663976d45E810B7C0E3B8B25564bD620d, "l2r"); + assertTrue(l2r.code.length > 0, "l2r code"); + assertEq(l2sgw, 0x25F753b06E1e092292e6773E119D00BEe5A1b8D4, "l2sgw"); + assertTrue(l2sgw.code.length > 0, "l2sgw code"); + assertEq(l2cgw, 0x4Ca25428D90D0813EC134b5160eb6301909B4A9B, "l2cgw"); + assertTrue(l2cgw.code.length > 0, "l2cgw code"); + assertEq(l2wgw, 0x29B1Fa62Af163E550Cb4173BE58787fa2d6456fF, "l2wgw"); + assertTrue(l2wgw.code.length > 0, "l2wgw code"); + assertEq(l2w, 0x7C9c18AE0EeA13600496D1222E8Ec22738b29C61, "l2w"); + assertTrue(l2w.code.length > 0, "l2w code"); + assertEq(l2pa, 0xf789F48Bc2c9ee6E98E564E6383B394ba6F9378c, "l2pa"); + assertTrue(l2pa.code.length > 0, "l2pa code"); + assertEq(l2bpf, 0x9446B15B1128aD326Ccf310a68F2FFB652D31934, "l2bpf"); + assertTrue(l2bpf.code.length > 0, "l2bpf code"); + assertEq(l2ue, 0xC85c71251E9354Cd6a8992BC02d968B04F4b55e6, "l2ue"); + assertTrue(l2ue.code.length > 0, "l2ue code"); + console2.log(l2mc); + // TODO: pull in fix-3 + // assertEq(l2mc, 0xfD402752767B4477549c8b95D4CfB4C9B4d636F1, "l2mc"); + // assertTrue(l2mc.code.length > 0, "l2mc code"); + } + } +} From 506e27b26add93637d2b4438af1dd36047d846a0 Mon Sep 17 00:00:00 2001 From: gzeon Date: Wed, 13 Dec 2023 11:16:10 +0800 Subject: [PATCH 31/48] refactor: scope chainId to reduce stack size --- .../ethereum/L1AtomicTokenBridgeCreator.sol | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol b/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol index 3f2c042771..549e73ff72 100644 --- a/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol +++ b/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol @@ -212,27 +212,29 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { revert L1AtomicTokenBridgeCreator_AlreadyCreated(); } - uint256 chainId = IRollupCore(address(IInbox(inbox).bridge().rollup())).chainId(); bool isUsingFeeToken = _getFeeToken(inbox) != address(0); // store L2 addresses before deployments L1DeploymentAddresses memory l1Deployment; L2DeploymentAddresses memory l2Deployment; - // store L2 addresses which are proxies - l2Deployment.router = _getProxyAddress(OrbitSalts.L2_ROUTER, chainId); - l2Deployment.standardGateway = _getProxyAddress(OrbitSalts.L2_STANDARD_GATEWAY, chainId); - l2Deployment.customGateway = _getProxyAddress(OrbitSalts.L2_CUSTOM_GATEWAY, chainId); - if (!isUsingFeeToken) { - l2Deployment.wethGateway = _getProxyAddress(OrbitSalts.L2_WETH_GATEWAY, chainId); - l2Deployment.weth = _getProxyAddress(OrbitSalts.L2_WETH, chainId); + { + // store L2 addresses which are proxies + uint256 chainId = IRollupCore(address(IInbox(inbox).bridge().rollup())).chainId(); + l2Deployment.router = _getProxyAddress(OrbitSalts.L2_ROUTER, chainId); + l2Deployment.standardGateway = _getProxyAddress(OrbitSalts.L2_STANDARD_GATEWAY, chainId); + l2Deployment.customGateway = _getProxyAddress(OrbitSalts.L2_CUSTOM_GATEWAY, chainId); + if (!isUsingFeeToken) { + l2Deployment.wethGateway = _getProxyAddress(OrbitSalts.L2_WETH_GATEWAY, chainId); + l2Deployment.weth = _getProxyAddress(OrbitSalts.L2_WETH, chainId); + } + l2Deployment.upgradeExecutor = _getProxyAddress(OrbitSalts.L2_EXECUTOR, chainId); + + // store L2 addresses which are not proxies + l2Deployment.proxyAdmin = _predictL2ProxyAdminAddress(chainId); + l2Deployment.beaconProxyFactory = _predictL2BeaconProxyFactoryAddress(chainId); + l2Deployment.multicall = _predictL2Multicall(chainId); } - l2Deployment.upgradeExecutor = _getProxyAddress(OrbitSalts.L2_EXECUTOR, chainId); - - // store L2 addresses which are not proxies - l2Deployment.proxyAdmin = _predictL2ProxyAdminAddress(chainId); - l2Deployment.beaconProxyFactory = _predictL2BeaconProxyFactoryAddress(chainId); - l2Deployment.multicall = _predictL2Multicall(chainId); // deploy L1 side of token bridge // get existing proxy admin and upgrade executor From 304fb02658db0666f259b7516afcd92fb58a51eb Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Tue, 12 Dec 2023 12:44:52 +0100 Subject: [PATCH 32/48] Add init check for StandardArbERC20 logic --- test-e2e/tokenBridgeDeploymentTest.ts | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/test-e2e/tokenBridgeDeploymentTest.ts b/test-e2e/tokenBridgeDeploymentTest.ts index c6f748a99a..b6cd728a4c 100644 --- a/test-e2e/tokenBridgeDeploymentTest.ts +++ b/test-e2e/tokenBridgeDeploymentTest.ts @@ -26,6 +26,9 @@ import { L2GatewayRouter__factory, L2WethGateway, L2WethGateway__factory, + StandardArbERC20__factory, + UpgradeableBeacon, + UpgradeableBeacon__factory, } from '../build/types' import { abi as UpgradeExecutorABI } from '@offchainlabs/upgrade-executor/build/contracts/src/UpgradeExecutor.sol/UpgradeExecutor.json' import { RollupCore__factory } from '@arbitrum/sdk/dist/lib/abi/factories/RollupCore__factory' @@ -347,7 +350,11 @@ async function checkL2StandardGatewayInitialization( l2Deployment.router.toLowerCase() ) - expect((await l2ERC20Gateway.beaconProxyFactory()).toLowerCase()).to.be.eq( + const beaconProxyFactory = BeaconProxyFactory__factory.connect( + await l2ERC20Gateway.beaconProxyFactory(), + l2Provider + ) + expect(beaconProxyFactory.address.toLowerCase()).to.be.eq( ( await L1ERC20Gateway__factory.connect( await l2ERC20Gateway.counterpartGateway(), @@ -364,6 +371,19 @@ async function checkL2StandardGatewayInitialization( ).cloneableProxyHash() ).toLowerCase() ) + + const beacon = UpgradeableBeacon__factory.connect( + await beaconProxyFactory.beacon(), + l2Provider + ) + expect(await beacon.owner()).to.be.eq(l2Deployment.upgradeExecutor) + + const standardArbERC20 = StandardArbERC20__factory.connect( + await beacon.implementation(), + l2Provider + ) + expect(await _isInitialized(standardArbERC20.address, l2Provider)).to.be.true + console.log(await _isInitialized(standardArbERC20.address, l2Provider)) } async function checkL2CustomGatewayInitialization( From fc9621edc129daa2b08fe3b1e7398b520a24233d Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Tue, 12 Dec 2023 12:59:32 +0100 Subject: [PATCH 33/48] Fix L2 multicall address prediction --- .../tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol | 2 +- test-e2e/tokenBridgeDeploymentTest.ts | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol b/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol index 549e73ff72..5e1c60b806 100644 --- a/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol +++ b/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol @@ -449,7 +449,7 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { function _predictL2Multicall(uint256 chainId) internal view returns (address) { return Create2.computeAddress( _getL2Salt(OrbitSalts.L2_MULTICALL, chainId), - l2MulticallTemplate.codehash, + keccak256(CreationCodeHelper.getCreationCodeFor(l2MulticallTemplate.code)), canonicalL2FactoryAddress ); } diff --git a/test-e2e/tokenBridgeDeploymentTest.ts b/test-e2e/tokenBridgeDeploymentTest.ts index b6cd728a4c..cf365cbfac 100644 --- a/test-e2e/tokenBridgeDeploymentTest.ts +++ b/test-e2e/tokenBridgeDeploymentTest.ts @@ -27,7 +27,6 @@ import { L2WethGateway, L2WethGateway__factory, StandardArbERC20__factory, - UpgradeableBeacon, UpgradeableBeacon__factory, } from '../build/types' import { abi as UpgradeExecutorABI } from '@offchainlabs/upgrade-executor/build/contracts/src/UpgradeExecutor.sol/UpgradeExecutor.json' @@ -47,6 +46,9 @@ const config = { let l1Provider: JsonRpcProvider let l2Provider: JsonRpcProvider +// when code at address is empty, ethers.js returns '0x' +const EMPTY_CODE_LENGTH = 2 + describe('tokenBridge', () => { it('should have deployed and initialized token bridge contracts', async function () { l1Provider = new JsonRpcProvider(config.l1Url) @@ -383,7 +385,6 @@ async function checkL2StandardGatewayInitialization( l2Provider ) expect(await _isInitialized(standardArbERC20.address, l2Provider)).to.be.true - console.log(await _isInitialized(standardArbERC20.address, l2Provider)) } async function checkL2CustomGatewayInitialization( @@ -429,7 +430,7 @@ async function checkL2WethGatewayInitialization( async function checkL2MulticallInitialization(l2Multicall: ArbMulticall2) { // check l2Multicall is deployed const l2MulticallCode = await l2Provider.getCode(l2Multicall.address) - expect(l2MulticallCode.length).to.be.gt(0) + expect(l2MulticallCode.length).to.be.gt(EMPTY_CODE_LENGTH) } async function checkL1Ownership( From 4e74ef2edd954d091848fbf806ec974981ec4737 Mon Sep 17 00:00:00 2001 From: gzeon Date: Tue, 12 Dec 2023 20:38:40 +0800 Subject: [PATCH 34/48] test: fix new test --- test-foundry/AtomicTokenBridgeFactory.t.sol | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test-foundry/AtomicTokenBridgeFactory.t.sol b/test-foundry/AtomicTokenBridgeFactory.t.sol index c9b7e90664..a032fdc038 100644 --- a/test-foundry/AtomicTokenBridgeFactory.t.sol +++ b/test-foundry/AtomicTokenBridgeFactory.t.sol @@ -233,10 +233,8 @@ contract AtomicTokenBridgeCreatorTest is Test { assertTrue(l2bpf.code.length > 0, "l2bpf code"); assertEq(l2ue, 0xC85c71251E9354Cd6a8992BC02d968B04F4b55e6, "l2ue"); assertTrue(l2ue.code.length > 0, "l2ue code"); - console2.log(l2mc); - // TODO: pull in fix-3 - // assertEq(l2mc, 0xfD402752767B4477549c8b95D4CfB4C9B4d636F1, "l2mc"); - // assertTrue(l2mc.code.length > 0, "l2mc code"); + assertEq(l2mc, 0x4572E7101b8A6d889680dA7CC35D6076e651e9fC, "l2mc"); + assertTrue(l2mc.code.length > 0, "l2mc code"); } } } From bebed618c3936e9637af4f756690928751e377d1 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Tue, 12 Dec 2023 14:05:06 +0100 Subject: [PATCH 35/48] If rollupOwner is a contract, alias its address when sending to L2 --- .../ethereum/L1AtomicTokenBridgeCreator.sol | 1 - .../ethereum/L1TokenBridgeRetryableSender.sol | 15 +++++++++++++-- test-e2e/tokenBridgeDeploymentTest.ts | 12 ++++++++++-- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol b/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol index 5e1c60b806..99ed46d5c6 100644 --- a/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol +++ b/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol @@ -31,7 +31,6 @@ import { } from "@offchainlabs/upgrade-executor/src/UpgradeExecutor.sol"; import {AddressAliasHelper} from "../libraries/AddressAliasHelper.sol"; import {IInbox, IBridge, IOwnable} from "@arbitrum/nitro-contracts/src/bridge/IInbox.sol"; -import {AddressAliasHelper} from "../libraries/AddressAliasHelper.sol"; import {ArbMulticall2} from "../../rpc-utils/MulticallV2.sol"; import {BeaconProxyFactory, ClonableBeaconProxy} from "../libraries/ClonableBeaconProxy.sol"; import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; diff --git a/contracts/tokenbridge/ethereum/L1TokenBridgeRetryableSender.sol b/contracts/tokenbridge/ethereum/L1TokenBridgeRetryableSender.sol index 1cba253ffc..95af0f65c5 100644 --- a/contracts/tokenbridge/ethereum/L1TokenBridgeRetryableSender.sol +++ b/contracts/tokenbridge/ethereum/L1TokenBridgeRetryableSender.sol @@ -16,6 +16,7 @@ import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {AddressAliasHelper} from "../libraries/AddressAliasHelper.sol"; /** * @title Token Bridge Retryable Ticket Sender @@ -49,13 +50,23 @@ contract L1TokenBridgeRetryableSender is Initializable, OwnableUpgradeable { address aliasedL1UpgradeExecutor, bool isUsingFeeToken ) external payable onlyOwner { + // rollupOwner address is provided to the L2 side so that it can be given the EXECUTOR role on the + // L2 UpgradeExecutor, in addition to alias(L1UpgradeExecutor). rollupOwner can be either EOA or a contract. + // If it is a contract, address needs to be aliased before sending to L2 in order to be usable. + address l2RollupOwner; + if (rollupOwner.code.length == 0) { + l2RollupOwner = rollupOwner; + } else { + l2RollupOwner = AddressAliasHelper.applyL1ToL2Alias(rollupOwner); + } + if (!isUsingFeeToken) { _sendRetryableUsingEth( retryableParams, l2, l1, l2StandardGatewayAddress, - rollupOwner, + l2RollupOwner, deployer, aliasedL1UpgradeExecutor ); @@ -66,7 +77,7 @@ contract L1TokenBridgeRetryableSender is Initializable, OwnableUpgradeable { l2, l1, l2StandardGatewayAddress, - rollupOwner, + l2RollupOwner, aliasedL1UpgradeExecutor ); } diff --git a/test-e2e/tokenBridgeDeploymentTest.ts b/test-e2e/tokenBridgeDeploymentTest.ts index cf365cbfac..1e5c48ac6d 100644 --- a/test-e2e/tokenBridgeDeploymentTest.ts +++ b/test-e2e/tokenBridgeDeploymentTest.ts @@ -309,8 +309,16 @@ async function checkL2UpgradeExecutorInitialization( const executorRole = await l2Executor.EXECUTOR_ROLE() expect(await l2Executor.hasRole(adminRole, l2Executor.address)).to.be.true - expect(await l2Executor.hasRole(executorRole, rollupAddresses.rollupOwner)).to - .be.true + + const isL1RollupOwnerContract = + (await l1Provider.getCode(rollupAddresses.rollupOwner)).length > + EMPTY_CODE_LENGTH + + const l2RollupOwner = isL1RollupOwnerContract + ? applyAlias(rollupAddresses.rollupOwner) + : rollupAddresses.rollupOwner + + expect(await l2Executor.hasRole(executorRole, l2RollupOwner)).to.be.true const aliasedL1Executor = applyAlias(rollupAddresses.upgradeExecutor) expect(await l2Executor.hasRole(executorRole, aliasedL1Executor)).to.be.true } From 8f4391896970dd2a4ed2e43c55babba8a1f01bf2 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Tue, 12 Dec 2023 17:45:08 +0100 Subject: [PATCH 36/48] Move aliasing rollupOwner to L1 creator --- .../ethereum/L1AtomicTokenBridgeCreator.sol | 15 +++++++++++---- .../ethereum/L1TokenBridgeRetryableSender.sol | 15 ++------------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol b/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol index 99ed46d5c6..05cc12d2b1 100644 --- a/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol +++ b/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol @@ -181,9 +181,10 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { * is called to issue 2nd retryable which deploys and inits the rest of the contracts. L2 chain is determined * by `inbox` parameter. * - * Token bridge can be deployed only once for certain inbox. Any further calls to `createTokenBridge` will revert - * because L1 salts are already used at that point and L1 contracts are already deployed at canonical addresses - * for that inbox. + * In addition to deploying token bridge contracts, L2 factory will also deploy UpgradeExector on L2 side. + * L2 UpgradeExecutor will set 2 accounts to have EXECUTOR role - rollupOwner and alias of L1UpgradeExecutor. + * 'rollupOwner' can be either EOA or a contract. If it is a contract, address will be aliased before sending to L2 + * in order to be usable. */ function createTokenBridge( address inbox, @@ -327,6 +328,12 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { uint256 fee = maxGasForContracts * gasPriceBid; IERC20(feeToken).safeTransferFrom(msg.sender, inbox, fee); } + + // alias rollup owner if it is a contract + address l2RollupOwner = rollupOwner.code.length == 0 + ? rollupOwner + : AddressAliasHelper.applyL1ToL2Alias(rollupOwner); + // sweep the balance to send the retryable and refund the difference // it is known that any eth previously in this contract can be extracted // tho it is not expected that this contract will have any eth @@ -350,7 +357,7 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { ), l1Deployment, l2Deployment.standardGateway, - rollupOwner, + l2RollupOwner, msg.sender, AddressAliasHelper.applyL1ToL2Alias(upgradeExecutor), isUsingFeeToken diff --git a/contracts/tokenbridge/ethereum/L1TokenBridgeRetryableSender.sol b/contracts/tokenbridge/ethereum/L1TokenBridgeRetryableSender.sol index 95af0f65c5..1cba253ffc 100644 --- a/contracts/tokenbridge/ethereum/L1TokenBridgeRetryableSender.sol +++ b/contracts/tokenbridge/ethereum/L1TokenBridgeRetryableSender.sol @@ -16,7 +16,6 @@ import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {AddressAliasHelper} from "../libraries/AddressAliasHelper.sol"; /** * @title Token Bridge Retryable Ticket Sender @@ -50,23 +49,13 @@ contract L1TokenBridgeRetryableSender is Initializable, OwnableUpgradeable { address aliasedL1UpgradeExecutor, bool isUsingFeeToken ) external payable onlyOwner { - // rollupOwner address is provided to the L2 side so that it can be given the EXECUTOR role on the - // L2 UpgradeExecutor, in addition to alias(L1UpgradeExecutor). rollupOwner can be either EOA or a contract. - // If it is a contract, address needs to be aliased before sending to L2 in order to be usable. - address l2RollupOwner; - if (rollupOwner.code.length == 0) { - l2RollupOwner = rollupOwner; - } else { - l2RollupOwner = AddressAliasHelper.applyL1ToL2Alias(rollupOwner); - } - if (!isUsingFeeToken) { _sendRetryableUsingEth( retryableParams, l2, l1, l2StandardGatewayAddress, - l2RollupOwner, + rollupOwner, deployer, aliasedL1UpgradeExecutor ); @@ -77,7 +66,7 @@ contract L1TokenBridgeRetryableSender is Initializable, OwnableUpgradeable { l2, l1, l2StandardGatewayAddress, - l2RollupOwner, + rollupOwner, aliasedL1UpgradeExecutor ); } From 79decd22f19c73cac7c94fa9a849d3ed041b74ba Mon Sep 17 00:00:00 2001 From: gzeon Date: Tue, 12 Dec 2023 20:32:16 +0800 Subject: [PATCH 37/48] test: deployment fail case --- test-foundry/AtomicTokenBridgeFactory.t.sol | 26 +++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test-foundry/AtomicTokenBridgeFactory.t.sol b/test-foundry/AtomicTokenBridgeFactory.t.sol index a032fdc038..ffb0c48e46 100644 --- a/test-foundry/AtomicTokenBridgeFactory.t.sol +++ b/test-foundry/AtomicTokenBridgeFactory.t.sol @@ -88,6 +88,11 @@ contract MockInbox is Test { } vm.startPrank(AddressAliasHelper.applyL1ToL2Alias(msg.sender)); if (to == address(0)) { + if (mode == 2) { + // mode 2: fail the deployment + vm.stopPrank(); + return 0; + } address addr; assembly { addr := create(0, add(data, 0x20), mload(data)) @@ -177,6 +182,27 @@ contract AtomicTokenBridgeCreatorTest is Test { _testDeployment(address(inbox)); } + function testDeploymentFailDeploy() public { + // although the deployment must have enough gas to deploy it can still fail due to gas price + // it such case the 2 retryable can be executed out-of-order + // Mode 2 simulate this case where the deployment fails and the call is executed first + MockInbox inbox = new MockInbox(2); + factory.createTokenBridge({ + inbox: address(inbox), + rollupOwner: address(this), + maxGasForContracts: 0, + gasPriceBid: 0 + }); + + // L2 Factory is not deployed in this case + address l2factory = factory.canonicalL2FactoryAddress(); + assertEq(l2factory, 0x20011A455c9eBBeD73CA307539D3e9Baff600fBD); + assertEq(l2factory.code.length, 0); + + inbox.setMode(0); // set back to normal mode + _testDeployment(address(inbox)); + } + function _testDeployment(address inbox) internal { factory.createTokenBridge({ inbox: address(inbox), From 525382ed606dbd96ec7ed1d77d076f0dcbee04b0 Mon Sep 17 00:00:00 2001 From: gzeon Date: Tue, 12 Dec 2023 20:34:39 +0800 Subject: [PATCH 38/48] feat: allow retry --- .../ethereum/L1AtomicTokenBridgeCreator.sol | 159 ++++++++++-------- 1 file changed, 86 insertions(+), 73 deletions(-) diff --git a/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol b/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol index 05cc12d2b1..86772159b0 100644 --- a/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol +++ b/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol @@ -56,7 +56,6 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { error L1AtomicTokenBridgeCreator_RollupOwnershipMisconfig(); error L1AtomicTokenBridgeCreator_ProxyAdminNotFound(); error L1AtomicTokenBridgeCreator_L2FactoryCannotBeChanged(); - error L1AtomicTokenBridgeCreator_AlreadyCreated(); event OrbitTokenBridgeCreated( address indexed inbox, @@ -208,9 +207,11 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { revert L1AtomicTokenBridgeCreator_RollupOwnershipMisconfig(); } - if (inboxToL1Deployment[inbox].router != address(0)) { - revert L1AtomicTokenBridgeCreator_AlreadyCreated(); - } + // we allow token bridge deployment to be retried + // this is useful to recover from expired or out-of-order retryables + // in case of retry, we assume L1 contracts already exist and we just need to deploy L2 contracts + // deployment mappings should not be updated in case of retry + bool isRetry = (inboxToL1Deployment[inbox].router != address(0)); bool isUsingFeeToken = _getFeeToken(inbox) != address(0); @@ -218,9 +219,13 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { L1DeploymentAddresses memory l1Deployment; L2DeploymentAddresses memory l2Deployment; + if (isRetry) { + l1Deployment = inboxToL1Deployment[inbox]; + } + { - // store L2 addresses which are proxies uint256 chainId = IRollupCore(address(IInbox(inbox).bridge().rollup())).chainId(); + // store L2 addresses which are proxies l2Deployment.router = _getProxyAddress(OrbitSalts.L2_ROUTER, chainId); l2Deployment.standardGateway = _getProxyAddress(OrbitSalts.L2_STANDARD_GATEWAY, chainId); l2Deployment.customGateway = _getProxyAddress(OrbitSalts.L2_CUSTOM_GATEWAY, chainId); @@ -243,82 +248,88 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { revert L1AtomicTokenBridgeCreator_ProxyAdminNotFound(); } - // l1 router deployment block - { - address routerTemplate = isUsingFeeToken - ? address(l1Templates.feeTokenBasedRouterTemplate) - : address(l1Templates.routerTemplate); - l1Deployment.router = _deployProxyWithSalt( - _getL1Salt(OrbitSalts.L1_ROUTER, inbox), routerTemplate, proxyAdmin - ); - } + if (!isRetry) { + // l1 router deployment block + { + address routerTemplate = isUsingFeeToken + ? address(l1Templates.feeTokenBasedRouterTemplate) + : address(l1Templates.routerTemplate); + l1Deployment.router = _deployProxyWithSalt( + _getL1Salt(OrbitSalts.L1_ROUTER, inbox), routerTemplate, proxyAdmin + ); + } - // l1 standard gateway deployment block - { - address template = isUsingFeeToken - ? address(l1Templates.feeTokenBasedStandardGatewayTemplate) - : address(l1Templates.standardGatewayTemplate); + // l1 standard gateway deployment block + { + address template = isUsingFeeToken + ? address(l1Templates.feeTokenBasedStandardGatewayTemplate) + : address(l1Templates.standardGatewayTemplate); - L1ERC20Gateway standardGateway = L1ERC20Gateway( - _deployProxyWithSalt( - _getL1Salt(OrbitSalts.L1_STANDARD_GATEWAY, inbox), template, proxyAdmin - ) - ); + L1ERC20Gateway standardGateway = L1ERC20Gateway( + _deployProxyWithSalt( + _getL1Salt(OrbitSalts.L1_STANDARD_GATEWAY, inbox), template, proxyAdmin + ) + ); - standardGateway.initialize( - l2Deployment.standardGateway, - l1Deployment.router, - inbox, - keccak256(type(ClonableBeaconProxy).creationCode), - l2Deployment.beaconProxyFactory - ); + standardGateway.initialize( + l2Deployment.standardGateway, + l1Deployment.router, + inbox, + keccak256(type(ClonableBeaconProxy).creationCode), + l2Deployment.beaconProxyFactory + ); - l1Deployment.standardGateway = address(standardGateway); - } + l1Deployment.standardGateway = address(standardGateway); + } - // l1 custom gateway deployment block - { - address template = isUsingFeeToken - ? address(l1Templates.feeTokenBasedCustomGatewayTemplate) - : address(l1Templates.customGatewayTemplate); + // l1 custom gateway deployment block + { + address template = isUsingFeeToken + ? address(l1Templates.feeTokenBasedCustomGatewayTemplate) + : address(l1Templates.customGatewayTemplate); - L1CustomGateway customGateway = L1CustomGateway( - _deployProxyWithSalt( - _getL1Salt(OrbitSalts.L1_CUSTOM_GATEWAY, inbox), template, proxyAdmin - ) - ); + L1CustomGateway customGateway = L1CustomGateway( + _deployProxyWithSalt( + _getL1Salt(OrbitSalts.L1_CUSTOM_GATEWAY, inbox), template, proxyAdmin + ) + ); - customGateway.initialize( - l2Deployment.customGateway, l1Deployment.router, inbox, upgradeExecutor - ); + customGateway.initialize( + l2Deployment.customGateway, l1Deployment.router, inbox, upgradeExecutor + ); - l1Deployment.customGateway = address(customGateway); - } + l1Deployment.customGateway = address(customGateway); + } - // l1 weth gateway deployment block - if (!isUsingFeeToken) { - L1WethGateway wethGateway = L1WethGateway( - payable( - _deployProxyWithSalt( - _getL1Salt(OrbitSalts.L1_WETH_GATEWAY, inbox), - address(l1Templates.wethGatewayTemplate), - proxyAdmin + // l1 weth gateway deployment block + if (!isUsingFeeToken) { + L1WethGateway wethGateway = L1WethGateway( + payable( + _deployProxyWithSalt( + _getL1Salt(OrbitSalts.L1_WETH_GATEWAY, inbox), + address(l1Templates.wethGatewayTemplate), + proxyAdmin + ) ) - ) - ); + ); - wethGateway.initialize( - l2Deployment.wethGateway, l1Deployment.router, inbox, l1Weth, l2Deployment.weth - ); + wethGateway.initialize( + l2Deployment.wethGateway, l1Deployment.router, inbox, l1Weth, l2Deployment.weth + ); - l1Deployment.wethGateway = address(wethGateway); - l1Deployment.weth = l1Weth; - } + l1Deployment.wethGateway = address(wethGateway); + l1Deployment.weth = l1Weth; + } - // init router - L1GatewayRouter(l1Deployment.router).initialize( - upgradeExecutor, l1Deployment.standardGateway, address(0), l2Deployment.router, inbox - ); + // init router + L1GatewayRouter(l1Deployment.router).initialize( + upgradeExecutor, + l1Deployment.standardGateway, + address(0), + l2Deployment.router, + inbox + ); + } // deploy factory and then L2 contracts through L2 factory, using 2 retryables calls _deployL2Factory(inbox, gasPriceBid, isUsingFeeToken); @@ -363,11 +374,13 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { isUsingFeeToken ); - emit OrbitTokenBridgeCreated( - inbox, rollupOwner, l1Deployment, l2Deployment, proxyAdmin, upgradeExecutor - ); - inboxToL1Deployment[inbox] = l1Deployment; - inboxToL2Deployment[inbox] = l2Deployment; + if (!isRetry) { + emit OrbitTokenBridgeCreated( + inbox, rollupOwner, l1Deployment, l2Deployment, proxyAdmin, upgradeExecutor + ); + inboxToL1Deployment[inbox] = l1Deployment; + inboxToL2Deployment[inbox] = l2Deployment; + } } /** From 1103c2928c11a09ca7ea821b5693c20854579934 Mon Sep 17 00:00:00 2001 From: gzeon Date: Tue, 12 Dec 2023 20:35:54 +0800 Subject: [PATCH 39/48] fix: typo --- test-foundry/AtomicTokenBridgeFactory.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-foundry/AtomicTokenBridgeFactory.t.sol b/test-foundry/AtomicTokenBridgeFactory.t.sol index ffb0c48e46..f0662ccb63 100644 --- a/test-foundry/AtomicTokenBridgeFactory.t.sol +++ b/test-foundry/AtomicTokenBridgeFactory.t.sol @@ -184,7 +184,7 @@ contract AtomicTokenBridgeCreatorTest is Test { function testDeploymentFailDeploy() public { // although the deployment must have enough gas to deploy it can still fail due to gas price - // it such case the 2 retryable can be executed out-of-order + // in such case the 2 retryable can be executed out-of-order // Mode 2 simulate this case where the deployment fails and the call is executed first MockInbox inbox = new MockInbox(2); factory.createTokenBridge({ From 974ec1d4d4d364e2112899386cd0ff2671bb0d6a Mon Sep 17 00:00:00 2001 From: gzeon Date: Tue, 12 Dec 2023 20:48:02 +0800 Subject: [PATCH 40/48] docs: more comments --- contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol b/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol index 86772159b0..4d318c9c88 100644 --- a/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol +++ b/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol @@ -219,6 +219,7 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { L1DeploymentAddresses memory l1Deployment; L2DeploymentAddresses memory l2Deployment; + // if retry, we assume L1 contracts already exist and we just need to deploy L2 contracts if (isRetry) { l1Deployment = inboxToL1Deployment[inbox]; } @@ -374,6 +375,7 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { isUsingFeeToken ); + // deployment mappings should not be updated in case of retry if (!isRetry) { emit OrbitTokenBridgeCreated( inbox, rollupOwner, l1Deployment, l2Deployment, proxyAdmin, upgradeExecutor From c3b266f02c64f50116a90030dd3cbe72cb9426cd Mon Sep 17 00:00:00 2001 From: gzeon Date: Tue, 12 Dec 2023 20:50:15 +0800 Subject: [PATCH 41/48] docs: explain more --- contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol b/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol index 4d318c9c88..c68a40bd39 100644 --- a/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol +++ b/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol @@ -219,7 +219,7 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { L1DeploymentAddresses memory l1Deployment; L2DeploymentAddresses memory l2Deployment; - // if retry, we assume L1 contracts already exist and we just need to deploy L2 contracts + // if retry, we use the existing l1 deployment if (isRetry) { l1Deployment = inboxToL1Deployment[inbox]; } @@ -249,6 +249,7 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { revert L1AtomicTokenBridgeCreator_ProxyAdminNotFound(); } + // if retry, we assume L1 contracts already exist if (!isRetry) { // l1 router deployment block { @@ -333,6 +334,7 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { } // deploy factory and then L2 contracts through L2 factory, using 2 retryables calls + // we do not care if it is a retry or not, if the L2 deployment already exists it will simply fail on L2 _deployL2Factory(inbox, gasPriceBid, isUsingFeeToken); if (isUsingFeeToken) { // transfer fee tokens to inbox to pay for 2nd retryable From 36f72ef6248d40a150c13c312e15df61e3488a7d Mon Sep 17 00:00:00 2001 From: gzeon Date: Tue, 12 Dec 2023 20:52:07 +0800 Subject: [PATCH 42/48] refactor: rename to resend --- .../ethereum/L1AtomicTokenBridgeCreator.sol | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol b/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol index c68a40bd39..bbbc35310f 100644 --- a/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol +++ b/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol @@ -207,11 +207,11 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { revert L1AtomicTokenBridgeCreator_RollupOwnershipMisconfig(); } - // we allow token bridge deployment to be retried + // we allow resending l2 deployment calls // this is useful to recover from expired or out-of-order retryables - // in case of retry, we assume L1 contracts already exist and we just need to deploy L2 contracts - // deployment mappings should not be updated in case of retry - bool isRetry = (inboxToL1Deployment[inbox].router != address(0)); + // in case of resend, we assume L1 contracts already exist and we just need to deploy L2 contracts + // deployment mappings should not be updated in case of resend + bool isResend = (inboxToL1Deployment[inbox].router != address(0)); bool isUsingFeeToken = _getFeeToken(inbox) != address(0); @@ -219,8 +219,8 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { L1DeploymentAddresses memory l1Deployment; L2DeploymentAddresses memory l2Deployment; - // if retry, we use the existing l1 deployment - if (isRetry) { + // if resend, we use the existing l1 deployment + if (isResend) { l1Deployment = inboxToL1Deployment[inbox]; } @@ -249,8 +249,8 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { revert L1AtomicTokenBridgeCreator_ProxyAdminNotFound(); } - // if retry, we assume L1 contracts already exist - if (!isRetry) { + // if resend, we assume L1 contracts already exist + if (!isResend) { // l1 router deployment block { address routerTemplate = isUsingFeeToken @@ -334,7 +334,7 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { } // deploy factory and then L2 contracts through L2 factory, using 2 retryables calls - // we do not care if it is a retry or not, if the L2 deployment already exists it will simply fail on L2 + // we do not care if it is a resend or not, if the L2 deployment already exists it will simply fail on L2 _deployL2Factory(inbox, gasPriceBid, isUsingFeeToken); if (isUsingFeeToken) { // transfer fee tokens to inbox to pay for 2nd retryable @@ -377,8 +377,8 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { isUsingFeeToken ); - // deployment mappings should not be updated in case of retry - if (!isRetry) { + // deployment mappings should not be updated in case of resend + if (!isResend) { emit OrbitTokenBridgeCreated( inbox, rollupOwner, l1Deployment, l2Deployment, proxyAdmin, upgradeExecutor ); From 88d61eb154c2e4ad4d3f3acbf0ba611252e6a1ca Mon Sep 17 00:00:00 2001 From: gzeon Date: Tue, 19 Dec 2023 16:51:43 +0800 Subject: [PATCH 43/48] docs: comment --- contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol b/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol index bbbc35310f..fedffe6494 100644 --- a/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol +++ b/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol @@ -225,8 +225,8 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { } { - uint256 chainId = IRollupCore(address(IInbox(inbox).bridge().rollup())).chainId(); // store L2 addresses which are proxies + uint256 chainId = IRollupCore(address(IInbox(inbox).bridge().rollup())).chainId(); l2Deployment.router = _getProxyAddress(OrbitSalts.L2_ROUTER, chainId); l2Deployment.standardGateway = _getProxyAddress(OrbitSalts.L2_STANDARD_GATEWAY, chainId); l2Deployment.customGateway = _getProxyAddress(OrbitSalts.L2_CUSTOM_GATEWAY, chainId); From 7ff1cf34fad1271d1315cc1f5ad76c7a9b74e4be Mon Sep 17 00:00:00 2001 From: gzeon Date: Tue, 19 Dec 2023 17:28:22 +0800 Subject: [PATCH 44/48] refactor: move alasing to retryable sender to reduce stack size --- contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol | 2 +- .../tokenbridge/ethereum/L1TokenBridgeRetryableSender.sol | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol b/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol index fedffe6494..9376f8ac29 100644 --- a/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol +++ b/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol @@ -373,7 +373,7 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { l2Deployment.standardGateway, l2RollupOwner, msg.sender, - AddressAliasHelper.applyL1ToL2Alias(upgradeExecutor), + upgradeExecutor, isUsingFeeToken ); diff --git a/contracts/tokenbridge/ethereum/L1TokenBridgeRetryableSender.sol b/contracts/tokenbridge/ethereum/L1TokenBridgeRetryableSender.sol index 1cba253ffc..06411905e7 100644 --- a/contracts/tokenbridge/ethereum/L1TokenBridgeRetryableSender.sol +++ b/contracts/tokenbridge/ethereum/L1TokenBridgeRetryableSender.sol @@ -7,6 +7,7 @@ import { L2RuntimeCode, ProxyAdmin } from "../arbitrum/L2AtomicTokenBridgeFactory.sol"; +import {AddressAliasHelper} from "../libraries/AddressAliasHelper.sol"; import { Initializable, OwnableUpgradeable @@ -46,9 +47,10 @@ contract L1TokenBridgeRetryableSender is Initializable, OwnableUpgradeable { address l2StandardGatewayAddress, address rollupOwner, address deployer, - address aliasedL1UpgradeExecutor, + address l1UpgradeExecutor, bool isUsingFeeToken ) external payable onlyOwner { + address aliasedL1UpgradeExecutor = AddressAliasHelper.applyL1ToL2Alias(l1UpgradeExecutor); if (!isUsingFeeToken) { _sendRetryableUsingEth( retryableParams, From f2a113368b58aeeef2dca5a08c31c8cedcf24c58 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Tue, 19 Dec 2023 10:38:50 +0100 Subject: [PATCH 45/48] Add warning to natspec --- .../tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol b/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol index 9376f8ac29..bf70d9e55e 100644 --- a/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol +++ b/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol @@ -45,8 +45,8 @@ import {IAccessControlUpgradeable} from /** * @title Layer1 token bridge creator - * @notice This contract is used to deploy token bridge on custom L2 chains. - * @dev Throughout the contract terms L1 and L2 are used, but those can be considered as base (N) chain and child (N+1) chain + * @notice This contract is used to deploy token bridge on custom Orbit chains. + * @dev Throughout the contract terms L1 and L2 are used, but those can be considered as parent (N) chain and child (N+1) chain. */ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { using SafeERC20 for IERC20; @@ -184,6 +184,9 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { * L2 UpgradeExecutor will set 2 accounts to have EXECUTOR role - rollupOwner and alias of L1UpgradeExecutor. * 'rollupOwner' can be either EOA or a contract. If it is a contract, address will be aliased before sending to L2 * in order to be usable. + * + * Warning: Due to asynchronous communication between parent and child chain, always check child chain contracts are + * fully deployed and initialized before sending tokens to the bridge. Otherwise tokens might be permanently lost. */ function createTokenBridge( address inbox, From e86d72d4a54d5a39b1e9c2fb544421a7a9fb8e7b Mon Sep 17 00:00:00 2001 From: TucksonDev Date: Tue, 19 Dec 2023 15:30:42 +0000 Subject: [PATCH 46/48] Add missing L1 UpgradeExecutor test --- test-e2e/tokenBridgeDeploymentTest.ts | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/test-e2e/tokenBridgeDeploymentTest.ts b/test-e2e/tokenBridgeDeploymentTest.ts index 1e5c48ac6d..9b8d221876 100644 --- a/test-e2e/tokenBridgeDeploymentTest.ts +++ b/test-e2e/tokenBridgeDeploymentTest.ts @@ -169,12 +169,19 @@ describe('tokenBridge', () => { ) } - const upgExecutor = new ethers.Contract( + const l1UpgradeExecutor = new ethers.Contract( + rollupAddresses.upgradeExecutor, + UpgradeExecutorABI, + l1Provider + ) + await checkL1UpgradeExecutorInitialization(l1UpgradeExecutor, rollupAddresses); + + const l2UpgradeExecutor = new ethers.Contract( l2Deployment.upgradeExecutor, UpgradeExecutorABI, l2Provider ) - await checkL2UpgradeExecutorInitialization(upgExecutor, rollupAddresses) + await checkL2UpgradeExecutorInitialization(l2UpgradeExecutor, rollupAddresses) await checkL1Ownership(l1Deployment, rollupAddresses) await checkL2Ownership(l2Deployment, usingFeeToken) @@ -298,6 +305,20 @@ async function checkL1WethGatewayInitialization( ) } +async function checkL1UpgradeExecutorInitialization( + l1Executor: Contract, + rollupAddresses: RollupAddresses +) { + console.log('checkL1UpgradeExecutorInitialization') + + //// check assigned/revoked roles are correctly set + const adminRole = await l1Executor.ADMIN_ROLE() + const executorRole = await l1Executor.EXECUTOR_ROLE() + + expect(await l1Executor.hasRole(adminRole, l1Executor.address)).to.be.true + expect(await l1Executor.hasRole(executorRole, rollupAddresses.rollupOwner)).to.be.true +} + async function checkL2UpgradeExecutorInitialization( l2Executor: Contract, rollupAddresses: RollupAddresses From 17908ebb1523fbba4c0294896e87ace7e549d146 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Thu, 21 Dec 2023 11:50:26 +0100 Subject: [PATCH 47/48] test: add foundry unit test coverage for token bridge factory (#64) * Draft implementation of constructor bytecode getter * Add function for getting the constructor bytecode * Check constructor size and throw error if unexpected one * Add constructor check rationale * Refactor * Add to CI * Check CI will fail if constructor is modified * Revert "Check CI will fail if constructor is modified" This reverts commit 6389b4a7827b193bb66bba9280d1fb0bb8b7cfda. * Clean up * Change _creationCodeFor so that it uses the same constructor as the one compiler would generate This will enable source code verification of the deployed token bridge contracts on the child chain * Init logic contract * Make deployment test more complete * Remove comment * Add deployment script * Calculate properly canonical address of L1 router * feat: remove canonicalL2FactoryAddress dependency * docs: explain * format: fix * Extract code creation function to lib * fix: remove create1 import * fix: fmt * feat: add create1 util * feat: remove logic salt * fix: interface name * Verify creation code generation * feat: cache and store deployment * refactor: lower contract size * feat: getTokenBridgeDeployment * Use chai test format * Add aeWETH/UpgradeExecutor constructor size check * feat: add e2e tests to CI (#18) * Add e2e tests to CI * Use custom testnode branch * Set testnode ref * Set correct ref * Specify main * Update comment Co-authored-by: gzeon * Remove intermediary function * Update out-of-date comments * feat: disable template update * chore: rename error * ci: wait-for-l3 * ci: switch back to main * Test canonical address for multicall, beacon proxy and proxy admin are correct * refactor: _deployProxyWithSalt * fix: remove Create1 lib and use empty salt instead * fix: beacon deploy * Update local deployment scripts * Add test info * Refactor test to support latest contract changes * Fetch rollup owner from logs * Update prod deployment script * Remove unnecessary structs * refactor: inline internal methods * refactor: flatten more * refactor: _computeAddressAtNonce0 * refactor: remove return val * refactor: separate l1 l2 deployment * feat: add deployment setter * fix: l2 multicall template * Bring deployment and test scripts up to date * docs: sendRetryable * fix: remove some redundent logic * Run e2e tests on chain that uses fee token * Change action name * fix: remove getCanonicalL1RouterAddress * fix: do not set weth when isUsingFeeToken * docs: add more comments * docs: _computeAddressAtNonce0 * refactor: trycatch * rename: event OrbitTokenBridgeDeploymentSet * feat: L1AtomicTokenBridgeCreator_AlreadyCreated * Remove unused error * Add L1AtomicTokenBridgeCreator init tests * Test setTemplates * refactor: calc salt in deploySeedProxy * refactor: inline oneline methods * Add L1AtomicTokenBridgeCreator init tests * Test setTemplates * Update nitro-contracts ref for testing * Add createTokenBridge tests * Add more L1 creator tests * Test ERC20 chain * Add skeleton for L2AtomicTokenBridgeFactory tests * Add more checks for L2 factory * fix: unsalted deployment * Complete L2 factory tests * Add init check for StandardArbERC20 logic * Fix L2 multicall address prediction * test: AtomicTokenBridgeCreator in foundry * test: deployment fail case * feat: allow retry * fix: typo * test: fix new test * docs: more comments * docs: explain more * refactor: rename to resend * If rollupOwner is a contract, alias its address when sending to L2 * Move aliasing rollupOwner to L1 creator * Add more L1 factory tests * Add missing L1 factory tests * refactor: scope chainId to reduce stack size * Stack too deep fix * Adapt tests --------- Co-authored-by: gzeon Co-authored-by: gzeon --- .gitmodules | 2 +- lib/nitro-contracts | 2 +- test-foundry/L1AtomicTokenBridgeCreator.t.sol | 864 ++++++++++++++++++ test-foundry/L1OrbitIntegration.t.sol | 2 +- test-foundry/L2AtomicTokenBridgeFactory.t.sol | 514 +++++++++++ 5 files changed, 1381 insertions(+), 3 deletions(-) create mode 100644 test-foundry/L1AtomicTokenBridgeCreator.t.sol create mode 100644 test-foundry/L2AtomicTokenBridgeFactory.t.sol diff --git a/.gitmodules b/.gitmodules index e19917d6cd..99139623ee 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,4 +4,4 @@ [submodule "lib/nitro-contracts"] path = lib/nitro-contracts url = git@github.com:OffchainLabs/nitro-contracts.git - branch = feature-orbit-bridge + branch = v1.1.0 diff --git a/lib/nitro-contracts b/lib/nitro-contracts index d5d33c2b8d..1a94dabd80 160000 --- a/lib/nitro-contracts +++ b/lib/nitro-contracts @@ -1 +1 @@ -Subproject commit d5d33c2b8d5615563b8c553ca2a1bb936c039924 +Subproject commit 1a94dabd805673e4c85e4071662814a142b20893 diff --git a/test-foundry/L1AtomicTokenBridgeCreator.t.sol b/test-foundry/L1AtomicTokenBridgeCreator.t.sol new file mode 100644 index 0000000000..95ddaf2e1a --- /dev/null +++ b/test-foundry/L1AtomicTokenBridgeCreator.t.sol @@ -0,0 +1,864 @@ +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.8.0; + +import "forge-std/Test.sol"; +import { + L1AtomicTokenBridgeCreator, + L1DeploymentAddresses, + L2DeploymentAddresses, + TransparentUpgradeableProxy, + ProxyAdmin, + ClonableBeaconProxy, + BeaconProxyFactory +} from "contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol"; +import {L1TokenBridgeRetryableSender} from + "contracts/tokenbridge/ethereum/L1TokenBridgeRetryableSender.sol"; +import {TestUtil} from "./util/TestUtil.sol"; +import {AddressAliasHelper} from "contracts/tokenbridge/libraries/AddressAliasHelper.sol"; +import {L1GatewayRouter} from "contracts/tokenbridge/ethereum/gateway/L1GatewayRouter.sol"; +import {L1ERC20Gateway} from "contracts/tokenbridge/ethereum/gateway/L1ERC20Gateway.sol"; +import {L1CustomGateway} from "contracts/tokenbridge/ethereum/gateway/L1CustomGateway.sol"; +import {L1WethGateway} from "contracts/tokenbridge/ethereum/gateway/L1WethGateway.sol"; +import {L1OrbitGatewayRouter} from "contracts/tokenbridge/ethereum/gateway/L1OrbitGatewayRouter.sol"; +import {L1OrbitERC20Gateway} from "contracts/tokenbridge/ethereum/gateway/L1OrbitERC20Gateway.sol"; +import {L1OrbitCustomGateway} from "contracts/tokenbridge/ethereum/gateway/L1OrbitCustomGateway.sol"; +import { + IUpgradeExecutor, + UpgradeExecutor +} from "@offchainlabs/upgrade-executor/src/UpgradeExecutor.sol"; +import {Inbox, IInboxBase} from "lib/nitro-contracts/src/bridge/Inbox.sol"; +import {ERC20Inbox} from "lib/nitro-contracts/src/bridge/ERC20Inbox.sol"; +import {IOutbox} from "lib/nitro-contracts/src/bridge/IOutbox.sol"; +import {Bridge, IBridge, IOwnable} from "lib/nitro-contracts/src/bridge/Bridge.sol"; +import {ERC20Bridge} from "lib/nitro-contracts/src/bridge/ERC20Bridge.sol"; +import { + RollupProxy, + IRollupUser, + IOutbox, + IRollupEventInbox, + IChallengeManager +} from "lib/nitro-contracts/src/rollup/RollupProxy.sol"; +import {RollupAdminLogic} from "lib/nitro-contracts/src/rollup/RollupAdminLogic.sol"; +import {RollupUserLogic} from "lib/nitro-contracts/src/rollup/RollupUserLogic.sol"; +import {Config, ContractDependencies} from "lib/nitro-contracts/src/rollup/Config.sol"; +import {ISequencerInbox} from "lib/nitro-contracts/src/bridge/ISequencerInbox.sol"; +import {ERC20PresetMinterPauser} from + "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; + +contract L1AtomicTokenBridgeCreatorTest is Test { + L1AtomicTokenBridgeCreator public l1Creator; + address public deployer = makeAddr("deployer"); + + function setUp() public { + l1Creator = L1AtomicTokenBridgeCreator( + TestUtil.deployProxy(address(new L1AtomicTokenBridgeCreator())) + ); + L1TokenBridgeRetryableSender sender = L1TokenBridgeRetryableSender( + TestUtil.deployProxy(address(new L1TokenBridgeRetryableSender())) + ); + + vm.deal(deployer, 10 ether); + vm.prank(deployer); + l1Creator.initialize(sender); + } + + /* solhint-disable func-name-mixedcase */ + function test_initialize() public { + L1AtomicTokenBridgeCreator _creator = L1AtomicTokenBridgeCreator( + TestUtil.deployProxy(address(new L1AtomicTokenBridgeCreator())) + ); + L1TokenBridgeRetryableSender _sender = L1TokenBridgeRetryableSender( + TestUtil.deployProxy(address(new L1TokenBridgeRetryableSender())) + ); + + vm.prank(deployer); + _creator.initialize(_sender); + + assertEq(_creator.owner(), deployer, "Wrong owner"); + assertEq(address(_creator.retryableSender()), address(_sender), "Wrong sender"); + assertEq(uint256(vm.load(address(_sender), 0)), 1, "Wrong init state"); + + address exepectedL2Factory = address( + uint160( + uint256( + keccak256( + abi.encodePacked( + bytes1(0xd6), + bytes1(0x94), + AddressAliasHelper.applyL1ToL2Alias(address(_creator)), + bytes1(0x80) + ) + ) + ) + ) + ); + assertEq( + address(_creator.canonicalL2FactoryAddress()), + exepectedL2Factory, + "Wrong canonicalL2FactoryAddress" + ); + } + + function test_initialize_revert_AlreadyInit() public { + L1AtomicTokenBridgeCreator _creator = L1AtomicTokenBridgeCreator( + TestUtil.deployProxy(address(new L1AtomicTokenBridgeCreator())) + ); + L1TokenBridgeRetryableSender _sender = new L1TokenBridgeRetryableSender(); + _creator.initialize(_sender); + + vm.expectRevert("Initializable: contract is already initialized"); + _creator.initialize(_sender); + } + + function test_initialize_revert_CantInitLogic() public { + L1AtomicTokenBridgeCreator _creator = new L1AtomicTokenBridgeCreator(); + + vm.expectRevert("Initializable: contract is already initialized"); + _creator.initialize(L1TokenBridgeRetryableSender(address(100))); + } + + function test_createTokenBridge_checkL1Router() public { + // prepare + _setTemplates(); + (RollupProxy rollup, Inbox inbox, ProxyAdmin pa, UpgradeExecutor upgExecutor) = + _createRollup(); + _createTokenBridge(rollup, inbox, upgExecutor); + + /// check state + (address l1RouterAddress, address standardGatewayAddress,,,) = + l1Creator.inboxToL1Deployment(address(inbox)); + + (L1GatewayRouter routerTemplate,,,,,,,) = l1Creator.l1Templates(); + + address expectedL1RouterAddress = Create2.computeAddress( + keccak256(abi.encodePacked(bytes("L1R"), address(inbox))), + keccak256( + abi.encodePacked( + type(TransparentUpgradeableProxy).creationCode, + abi.encode(address(routerTemplate), pa, bytes("")) + ) + ), + address(l1Creator) + ); + assertEq(l1RouterAddress, expectedL1RouterAddress, "Wrong l1Router address"); + assertTrue(l1RouterAddress.code.length > 0, "Wrong l1Router code"); + + L1GatewayRouter l1Router = L1GatewayRouter(l1RouterAddress); + assertEq(l1Router.owner(), address(upgExecutor), "Wrong l1Router owner"); + assertEq(l1Router.defaultGateway(), standardGatewayAddress, "Wrong l1Router defaultGateway"); + assertEq(l1Router.whitelist(), address(0), "Wrong l1Router whitelist"); + + (address l2Router,,,,,,,,) = l1Creator.inboxToL2Deployment(address(inbox)); + assertEq(l1Router.counterpartGateway(), l2Router, "Wrong l1Router counterpartGateway"); + assertEq(l1Router.inbox(), address(inbox), "Wrong l1Router inbox"); + } + + function test_createTokenBridge_checkL1StandardGateway() public { + // prepare + _setTemplates(); + (RollupProxy rollup, Inbox inbox, ProxyAdmin pa, UpgradeExecutor upgExecutor) = + _createRollup(); + _createTokenBridge(rollup, inbox, upgExecutor); + + /// check state + (address l1RouterAddress, address l1StandardGatewayAddress,,,) = + l1Creator.inboxToL1Deployment(address(inbox)); + + (, L1ERC20Gateway standardGatewayTemplate,,,,,,) = l1Creator.l1Templates(); + + address expectedL1StandardGatewayAddress = Create2.computeAddress( + keccak256(abi.encodePacked(bytes("L1SGW"), address(inbox))), + keccak256( + abi.encodePacked( + type(TransparentUpgradeableProxy).creationCode, + abi.encode(address(standardGatewayTemplate), pa, bytes("")) + ) + ), + address(l1Creator) + ); + assertEq( + l1StandardGatewayAddress, + expectedL1StandardGatewayAddress, + "Wrong l1StandardGateway address" + ); + assertTrue(l1StandardGatewayAddress.code.length > 0, "Wrong l1StandardGateway code"); + + L1ERC20Gateway l1StandardGateway = L1ERC20Gateway(l1StandardGatewayAddress); + (, address l2StandardGateway,,,,,,,) = l1Creator.inboxToL2Deployment(address(inbox)); + assertEq( + l1StandardGateway.counterpartGateway(), + l2StandardGateway, + "Wrong l1StandardGateway counterpartGateway" + ); + assertEq(l1StandardGateway.router(), l1RouterAddress, "Wrong l1StandardGateway router"); + assertEq(l1StandardGateway.inbox(), address(inbox), "Wrong l1StandardGateway inbox"); + assertEq( + l1StandardGateway.cloneableProxyHash(), + keccak256(type(ClonableBeaconProxy).creationCode), + "Wrong l1StandardGateway cloneableProxyHash" + ); + + address expectedL2BeaconProxyFactoryAddress = Create2.computeAddress( + keccak256( + abi.encodePacked( + bytes("L2BPF"), + uint256(2000), + AddressAliasHelper.applyL1ToL2Alias(address(l1Creator.retryableSender())) + ) + ), + keccak256(type(BeaconProxyFactory).creationCode), + l1Creator.canonicalL2FactoryAddress() + ); + assertEq( + l1StandardGateway.l2BeaconProxyFactory(), + expectedL2BeaconProxyFactoryAddress, + "Wrong l1StandardGateway l2BeaconProxyFactory" + ); + } + + function test_createTokenBridge_checkL1CustomGateway() public { + // prepare + _setTemplates(); + (RollupProxy rollup, Inbox inbox, ProxyAdmin pa, UpgradeExecutor upgExecutor) = + _createRollup(); + _createTokenBridge(rollup, inbox, upgExecutor); + + /// check state + (address l1RouterAddress,, address l1CustomGatewayAddress,,) = + l1Creator.inboxToL1Deployment(address(inbox)); + + (,, L1CustomGateway customGatewayTemplate,,,,,) = l1Creator.l1Templates(); + + address expectedL1CustomGatewayAddress = Create2.computeAddress( + keccak256(abi.encodePacked(bytes("L1CGW"), address(inbox))), + keccak256( + abi.encodePacked( + type(TransparentUpgradeableProxy).creationCode, + abi.encode(address(customGatewayTemplate), pa, bytes("")) + ) + ), + address(l1Creator) + ); + assertEq( + l1CustomGatewayAddress, + expectedL1CustomGatewayAddress, + "Wrong l1StandardGateway address" + ); + assertTrue(l1CustomGatewayAddress.code.length > 0, "Wrong l1CustomGatewayAddress code"); + + L1CustomGateway l1CustomGateway = L1CustomGateway(l1CustomGatewayAddress); + (,, address l2CustomGateway,,,,,,) = l1Creator.inboxToL2Deployment(address(inbox)); + assertEq( + l1CustomGateway.counterpartGateway(), + l2CustomGateway, + "Wrong l1CustomGateway counterpartGateway" + ); + assertEq(l1CustomGateway.router(), l1RouterAddress, "Wrong l1CustomGateway router"); + assertEq(l1CustomGateway.inbox(), address(inbox), "Wrong l1CustomGateway inbox"); + assertEq(l1CustomGateway.owner(), address(upgExecutor), "Wrong l1CustomGateway owner"); + } + + function test_createTokenBridge_checkL1WethGateway() public { + // prepare + _setTemplates(); + (RollupProxy rollup, Inbox inbox, ProxyAdmin pa, UpgradeExecutor upgExecutor) = + _createRollup(); + _createTokenBridge(rollup, inbox, upgExecutor); + + /// check state + (address l1RouterAddress,,, address l1WethGatewayAddress,) = + l1Creator.inboxToL1Deployment(address(inbox)); + + (,,, L1WethGateway wethGatewayTemplate,,,,) = l1Creator.l1Templates(); + + address expectedL1WethGatewayAddress = Create2.computeAddress( + keccak256(abi.encodePacked(bytes("L1WGW"), address(inbox))), + keccak256( + abi.encodePacked( + type(TransparentUpgradeableProxy).creationCode, + abi.encode(address(wethGatewayTemplate), pa, bytes("")) + ) + ), + address(l1Creator) + ); + assertEq(l1WethGatewayAddress, expectedL1WethGatewayAddress, "Wrong l1WethGatewayAddresss"); + assertTrue(l1WethGatewayAddress.code.length > 0, "Wrong l1WethGatewayAddress code"); + + L1WethGateway l1WethGateway = L1WethGateway(payable(l1WethGatewayAddress)); + (,,, address l2WethGateway, address l2Weth,,,,) = + l1Creator.inboxToL2Deployment(address(inbox)); + assertEq( + l1WethGateway.counterpartGateway(), + l2WethGateway, + "Wrong l1WethGateway counterpartGateway" + ); + assertEq(l1WethGateway.router(), l1RouterAddress, "Wrong l1WethGateway router"); + assertEq(l1WethGateway.inbox(), address(inbox), "Wrong l1WethGateway inbox"); + assertEq(l1WethGateway.l1Weth(), l1Creator.l1Weth(), "Wrong l1WethGateway l1Weth"); + assertEq(l1WethGateway.l2Weth(), l2Weth, "Wrong l1WethGateway l2Weth"); + } + + function test_createTokenBridge_DeployerIsRefunded() public { + // prepare + _setTemplates(); + (RollupProxy rollup, Inbox inbox, ProxyAdmin pa, UpgradeExecutor upgExecutor) = + _createRollup(); + + uint256 deployerBalanceBefore = deployer.balance; + + _createTokenBridge(rollup, inbox, upgExecutor); + + uint256 deployerBalanceAfter = deployer.balance; + + assertGt(deployerBalanceAfter, deployerBalanceBefore - 1 ether, "Refund not received"); + } + + function test_createTokenBridge_ERC20Chain() public { + // prepare + _setTemplates(); + (RollupProxy rollup, ERC20Inbox inbox,, UpgradeExecutor upgExecutor, ERC20 nativeToken) = + _createERC20Rollup(); + + { + // mock owner() => upgExecutor + vm.mockCall( + address(rollup), + abi.encodeWithSignature("owner()"), + abi.encode(address(upgExecutor)) + ); + + // mock rollupOwner is executor on upgExecutor + vm.mockCall( + address(upgExecutor), + abi.encodeWithSignature( + "hasRole(bytes32,address)", upgExecutor.EXECUTOR_ROLE(), deployer + ), + abi.encode(true) + ); + + // mock chain id + uint256 mockChainId = 2000; + vm.mockCall( + address(rollup), abi.encodeWithSignature("chainId()"), abi.encode(mockChainId) + ); + } + + /// do it + vm.deal(deployer, 1 ether); + vm.startPrank(deployer); + nativeToken.approve(address(l1Creator), 10 ether); + l1Creator.createTokenBridge(address(inbox), deployer, 100, 200); + + /// check state + { + ( + address l1Router, + address l1StandardGateway, + address l1CustomGateway, + address l1WethGateway, + address l1Weth + ) = l1Creator.inboxToL1Deployment(address(inbox)); + assertTrue(l1Router != address(0), "Wrong l1Router"); + assertTrue(l1StandardGateway != address(0), "Wrong l1StandardGateway"); + assertTrue(l1CustomGateway != address(0), "Wrong l1CustomGateway"); + assertTrue(l1WethGateway == address(0), "Wrong l1WethGateway"); + assertTrue(l1Weth == address(0), "Wrong l1Weth"); + } + + { + ( + address l2Router, + address l2StandardGateway, + address l2CustomGateway, + address l2WethGateway, + address l2Weth, + address l2ProxyAdmin, + address l2BeaconProxyFactory, + address l2UpgradeExecutor, + address l2Multicall + ) = l1Creator.inboxToL2Deployment(address(inbox)); + assertTrue(l2Router != address(0), "Wrong l2Router"); + assertTrue(l2StandardGateway != address(0), "Wrong l2StandardGateway"); + assertTrue(l2CustomGateway != address(0), "Wrong l2CustomGateway"); + assertTrue(l2WethGateway == address(0), "Wrong l2WethGateway"); + assertTrue(l2Weth == address(0), "Wrong l2Weth"); + assertTrue(l2ProxyAdmin != address(0), "Wrong l2ProxyAdmin"); + assertTrue(l2BeaconProxyFactory != address(0), "Wrong l2BeaconProxyFactory"); + assertTrue(l2UpgradeExecutor != address(0), "Wrong l2UpgradeExecutor"); + assertTrue(l2Multicall != address(0), "Wrong l2Multicall"); + } + } + + function test_createTokenBridge_revert_TemplatesNotSet() public { + vm.expectRevert( + abi.encodeWithSelector( + L1AtomicTokenBridgeCreator.L1AtomicTokenBridgeCreator_TemplatesNotSet.selector + ) + ); + l1Creator.createTokenBridge(address(100), address(101), 100, 200); + } + + function test_createTokenBridge_revert_RollupOwnershipMisconfig() public { + // prepare + _setTemplates(); + (RollupProxy rollup, Inbox inbox,, UpgradeExecutor upgExecutor) = _createRollup(); + + // mock owner() => upgExecutor + vm.mockCall( + address(rollup), abi.encodeWithSignature("owner()"), abi.encode(address(upgExecutor)) + ); + + // expect revert when creating bridge + vm.expectRevert( + abi.encodeWithSelector( + L1AtomicTokenBridgeCreator + .L1AtomicTokenBridgeCreator_RollupOwnershipMisconfig + .selector + ) + ); + l1Creator.createTokenBridge(address(inbox), deployer, 100, 200); + } + + function test_getRouter_NonExistent() public { + assertEq(l1Creator.getRouter(makeAddr("non-existent")), address(0), "Should be empty"); + } + + function test_getRouter() public { + // prepare + _setTemplates(); + (RollupProxy rollup, Inbox inbox,, UpgradeExecutor upgExecutor) = _createRollup(); + + { + // mock owner() => upgExecutor + vm.mockCall( + address(rollup), + abi.encodeWithSignature("owner()"), + abi.encode(address(upgExecutor)) + ); + + // mock rollupOwner is executor on upgExecutor + vm.mockCall( + address(upgExecutor), + abi.encodeWithSignature( + "hasRole(bytes32,address)", upgExecutor.EXECUTOR_ROLE(), deployer + ), + abi.encode(true) + ); + + // mock chain id + uint256 mockChainId = 2000; + vm.mockCall( + address(rollup), abi.encodeWithSignature("chainId()"), abi.encode(mockChainId) + ); + } + + /// do it + vm.deal(deployer, 10 ether); + vm.prank(deployer); + l1Creator.createTokenBridge{value: 1 ether}(address(inbox), deployer, 100, 200); + + /// state check + (address expectedRouter,,,,) = l1Creator.inboxToL1Deployment(address(inbox)); + assertEq(l1Creator.getRouter(address(inbox)), expectedRouter, "Wrong router"); + } + + function test_setDeployment() public { + (RollupProxy rollup, Inbox inbox,, UpgradeExecutor upgExecutor) = _createRollup(); + + // mock owner() => upgExecutor + vm.mockCall( + address(rollup), abi.encodeWithSignature("owner()"), abi.encode(address(upgExecutor)) + ); + + L1DeploymentAddresses memory l1 = L1DeploymentAddresses( + makeAddr("l1Router"), + makeAddr("l1StandardGateway"), + makeAddr("l1CustomGateway"), + makeAddr("l1WethGateway"), + makeAddr("l1Weth") + ); + + L2DeploymentAddresses memory l2 = L2DeploymentAddresses( + makeAddr("l2Router"), + makeAddr("l2StandardGateway"), + makeAddr("l2CustomGateway"), + makeAddr("l2WethGateway"), + makeAddr("l2Weth"), + makeAddr("l2ProxyAdmin"), + makeAddr("l2BeaconProxyFactory"), + makeAddr("l2UpgradeExecutor"), + makeAddr("l2Multicall") + ); + + /// expect event + vm.expectEmit(true, true, true, true); + emit OrbitTokenBridgeDeploymentSet(address(inbox), l1, l2); + + /// do it + vm.prank(address(upgExecutor)); + l1Creator.setDeployment(address(inbox), l1, l2); + + /// check state + { + ( + address l1Router, + address l1StandardGateway, + address l1CustomGateway, + address l1WethGateway, + address l1Weth + ) = l1Creator.inboxToL1Deployment(address(inbox)); + assertEq(l1Router, l1.router, "Wrong l1Router"); + assertEq(l1StandardGateway, l1.standardGateway, "Wrong l1StandardGateway"); + assertEq(l1CustomGateway, l1.customGateway, "Wrong l1CustomGateway"); + assertEq(l1WethGateway, l1.wethGateway, "Wrong l1WethGateway"); + assertEq(l1Weth, l1.weth, "Wrong l1Weth"); + } + + { + ( + address l2Router, + address l2StandardGateway, + address l2CustomGateway, + address l2WethGateway, + address l2Weth, + address l2ProxyAdmin, + address l2BeaconProxyFactory, + address l2UpgradeExecutor, + address l2Multicall + ) = l1Creator.inboxToL2Deployment(address(inbox)); + assertEq(l2Router, l2.router, "Wrong l2Router"); + assertEq(l2StandardGateway, l2.standardGateway, "Wrong l2StandardGateway"); + assertEq(l2CustomGateway, l2.customGateway, "Wrong l2CustomGateway"); + assertEq(l2WethGateway, l2.wethGateway, "Wrong l2WethGateway"); + assertEq(l2Weth, l2.weth, "Wrong l2Weth"); + assertEq(l2ProxyAdmin, l2.proxyAdmin, "Wrong l2ProxyAdmin"); + assertEq(l2Weth, l2.weth, "Wrong l2Weth"); + assertEq(l2BeaconProxyFactory, l2.beaconProxyFactory, "Wrong l2BeaconProxyFactory"); + assertEq(l2UpgradeExecutor, l2.upgradeExecutor, "Wrong l2UpgradeExecutor"); + assertEq(l2Multicall, l2.multicall, "Wrong l2Multicall"); + } + } + + function test_setDeployment_revert_OnlyRollupOwner() public { + (RollupProxy rollup, Inbox inbox,, UpgradeExecutor upgExecutor) = _createRollup(); + + // mock owner() => upgExecutor + vm.mockCall( + address(rollup), abi.encodeWithSignature("owner()"), abi.encode(address(upgExecutor)) + ); + + L1DeploymentAddresses memory l1 = L1DeploymentAddresses( + makeAddr("l1Router"), + makeAddr("l1StandardGateway"), + makeAddr("l1CustomGateway"), + makeAddr("l1WethGateway"), + makeAddr("l1Weth") + ); + + L2DeploymentAddresses memory l2 = L2DeploymentAddresses( + makeAddr("l2Router"), + makeAddr("l2StandardGateway"), + makeAddr("l2CustomGateway"), + makeAddr("l2WethGateway"), + makeAddr("l2Weth"), + makeAddr("l2ProxyAdmin"), + makeAddr("l2BeaconProxyFactory"), + makeAddr("l2UpgradeExecutor"), + makeAddr("l2Multicall") + ); + + vm.expectRevert( + abi.encodeWithSelector( + L1AtomicTokenBridgeCreator.L1AtomicTokenBridgeCreator_OnlyRollupOwner.selector + ) + ); + l1Creator.setDeployment(address(inbox), l1, l2); + } + + function test_setTemplates() public { + L1AtomicTokenBridgeCreator.L1Templates memory _l1Templates = L1AtomicTokenBridgeCreator + .L1Templates( + new L1GatewayRouter(), + new L1ERC20Gateway(), + new L1CustomGateway(), + new L1WethGateway(), + new L1OrbitGatewayRouter(), + new L1OrbitERC20Gateway(), + new L1OrbitCustomGateway(), + new UpgradeExecutor() + ); + + vm.expectEmit(true, true, true, true); + emit OrbitTokenBridgeTemplatesUpdated(); + + vm.prank(deployer); + l1Creator.setTemplates( + _l1Templates, + makeAddr("_l2TokenBridgeFactoryTemplate"), + makeAddr("_l2RouterTemplate"), + makeAddr("_l2StandardGatewayTemplate"), + makeAddr("_l2CustomGatewayTemplate"), + makeAddr("_l2WethGatewayTemplate"), + makeAddr("_l2WethTemplate"), + makeAddr("_l2MulticallTemplate"), + makeAddr("_l1Weth"), + makeAddr("_l1Multicall"), + 1000 + ); + + ( + L1GatewayRouter router, + L1ERC20Gateway gw, + L1CustomGateway customGw, + L1WethGateway wGw, + L1OrbitGatewayRouter oRouter, + L1OrbitERC20Gateway oGw, + L1OrbitCustomGateway oCustomGw, + IUpgradeExecutor executor + ) = l1Creator.l1Templates(); + assertEq(address(router), address(_l1Templates.routerTemplate), "Wrong templates"); + assertEq(address(gw), address(_l1Templates.standardGatewayTemplate), "Wrong templates"); + assertEq(address(customGw), address(_l1Templates.customGatewayTemplate), "Wrong templates"); + assertEq(address(wGw), address(_l1Templates.wethGatewayTemplate), "Wrong templates"); + assertEq(address(oRouter), address(_l1Templates.feeTokenBasedRouterTemplate), "Wrong temp"); + assertEq( + address(oGw), address(_l1Templates.feeTokenBasedStandardGatewayTemplate), "Wrong gw" + ); + assertEq( + address(oCustomGw), address(_l1Templates.feeTokenBasedCustomGatewayTemplate), "Wrong gw" + ); + assertEq(address(executor), address(_l1Templates.upgradeExecutor), "Wrong executor"); + + assertEq( + l1Creator.l2TokenBridgeFactoryTemplate(), + makeAddr("_l2TokenBridgeFactoryTemplate"), + "Wrong ref" + ); + assertEq(l1Creator.l2RouterTemplate(), makeAddr("_l2RouterTemplate"), "Wrong ref"); + assertEq( + l1Creator.l2StandardGatewayTemplate(), + makeAddr("_l2StandardGatewayTemplate"), + "Wrong ref" + ); + assertEq( + l1Creator.l2CustomGatewayTemplate(), makeAddr("_l2CustomGatewayTemplate"), "Wrong ref" + ); + assertEq(l1Creator.l2WethGatewayTemplate(), makeAddr("_l2WethGatewayTemplate"), "Wrong ref"); + assertEq(l1Creator.l2WethTemplate(), makeAddr("_l2WethTemplate"), "Wrong ref"); + assertEq(l1Creator.l2MulticallTemplate(), makeAddr("_l2MulticallTemplate"), "Wrong ref"); + assertEq(l1Creator.l1Weth(), makeAddr("_l1Weth"), "Wrong ref"); + assertEq(l1Creator.l1Multicall(), makeAddr("_l1Multicall"), "Wrong ref"); + assertEq(l1Creator.gasLimitForL2FactoryDeployment(), 1000, "Wrong ref"); + } + + function test_setTemplates_revert_OnlyOwner() public { + L1AtomicTokenBridgeCreator.L1Templates memory _l1Templates = L1AtomicTokenBridgeCreator + .L1Templates( + new L1GatewayRouter(), + new L1ERC20Gateway(), + new L1CustomGateway(), + new L1WethGateway(), + new L1OrbitGatewayRouter(), + new L1OrbitERC20Gateway(), + new L1OrbitCustomGateway(), + new UpgradeExecutor() + ); + + vm.expectRevert("Ownable: caller is not the owner"); + l1Creator.setTemplates( + _l1Templates, + address(0), + address(0), + address(0), + address(0), + address(0), + address(0), + address(0), + address(0), + address(0), + 1000 + ); + } + + function test_setTemplates_revert_L2FactoryCannotBeChanged() public { + L1AtomicTokenBridgeCreator.L1Templates memory _l1Templates = L1AtomicTokenBridgeCreator + .L1Templates( + new L1GatewayRouter(), + new L1ERC20Gateway(), + new L1CustomGateway(), + new L1WethGateway(), + new L1OrbitGatewayRouter(), + new L1OrbitERC20Gateway(), + new L1OrbitCustomGateway(), + new UpgradeExecutor() + ); + + address originalL2Factory = makeAddr("originalL2Factory"); + + vm.prank(deployer); + l1Creator.setTemplates( + _l1Templates, + originalL2Factory, + address(0), + address(0), + address(0), + address(0), + address(0), + address(0), + address(0), + address(0), + 1000 + ); + + address newL2FactoryTemplate = makeAddr("newL2FactoryTemplate"); + vm.expectRevert( + abi.encodeWithSelector( + L1AtomicTokenBridgeCreator + .L1AtomicTokenBridgeCreator_L2FactoryCannotBeChanged + .selector + ) + ); + vm.prank(deployer); + l1Creator.setTemplates( + _l1Templates, + newL2FactoryTemplate, + address(0), + address(0), + address(0), + address(0), + address(0), + address(0), + address(0), + address(0), + 1000 + ); + } + + function _createRollup() + internal + returns (RollupProxy rollup, Inbox inbox, ProxyAdmin pa, UpgradeExecutor upgExecutor) + { + pa = new ProxyAdmin(); + rollup = new RollupProxy(); + upgExecutor = new UpgradeExecutor(); + + Bridge bridge = + Bridge(address(new TransparentUpgradeableProxy(address(new Bridge()), address(pa), ""))); + inbox = Inbox( + address(new TransparentUpgradeableProxy(address(new Inbox(104_857)), address(pa), "")) + ); + + inbox.initialize(IBridge(address(bridge)), ISequencerInbox(makeAddr("sequencerInbox"))); + bridge.initialize(IOwnable(address(rollup))); + + vm.mockCall(address(rollup), abi.encodeWithSignature("owner()"), abi.encode(address(this))); + bridge.setDelayedInbox(address(inbox), true); + } + + function _createERC20Rollup() + internal + returns ( + RollupProxy rollup, + ERC20Inbox inbox, + ProxyAdmin pa, + UpgradeExecutor upgExecutor, + ERC20 nativeToken + ) + { + pa = new ProxyAdmin(); + rollup = new RollupProxy(); + upgExecutor = new UpgradeExecutor(); + + ERC20Bridge bridge = ERC20Bridge( + address(new TransparentUpgradeableProxy(address(new ERC20Bridge()), address(pa), "")) + ); + inbox = ERC20Inbox( + address( + new TransparentUpgradeableProxy(address(new ERC20Inbox(104_857)), address(pa), "") + ) + ); + + nativeToken = ERC20(address(new ERC20PresetMinterPauser("X", "Y"))); + ERC20PresetMinterPauser(address(nativeToken)).mint(deployer, 10 ether); + + bridge.initialize(IOwnable(address(rollup)), address(nativeToken)); + inbox.initialize(IBridge(address(bridge)), ISequencerInbox(makeAddr("sequencerInbox"))); + + vm.mockCall(address(rollup), abi.encodeWithSignature("owner()"), abi.encode(address(this))); + bridge.setDelayedInbox(address(inbox), true); + } + + function _createTokenBridge(RollupProxy rollup, Inbox inbox, UpgradeExecutor upgExecutor) + internal + { + // mock owner() => upgExecutor + vm.mockCall( + address(rollup), abi.encodeWithSignature("owner()"), abi.encode(address(upgExecutor)) + ); + + // mock rollupOwner is executor on upgExecutor + vm.mockCall( + address(upgExecutor), + abi.encodeWithSignature( + "hasRole(bytes32,address)", upgExecutor.EXECUTOR_ROLE(), deployer + ), + abi.encode(true) + ); + + // mock chain id + uint256 mockChainId = 2000; + vm.mockCall(address(rollup), abi.encodeWithSignature("chainId()"), abi.encode(mockChainId)); + + // create token bridge + vm.prank(deployer); + l1Creator.createTokenBridge{value: 1 ether}(address(inbox), deployer, 100, 200); + } + + function _setTemplates() internal { + L1AtomicTokenBridgeCreator.L1Templates memory _l1Templates = L1AtomicTokenBridgeCreator + .L1Templates( + new L1GatewayRouter(), + new L1ERC20Gateway(), + new L1CustomGateway(), + new L1WethGateway(), + new L1OrbitGatewayRouter(), + new L1OrbitERC20Gateway(), + new L1OrbitCustomGateway(), + new UpgradeExecutor() + ); + + vm.prank(deployer); + l1Creator.setTemplates( + _l1Templates, + makeAddr("_l2TokenBridgeFactoryTemplate"), + makeAddr("_l2RouterTemplate"), + makeAddr("_l2StandardGatewayTemplate"), + makeAddr("_l2CustomGatewayTemplate"), + makeAddr("_l2WethGatewayTemplate"), + makeAddr("_l2WethTemplate"), + makeAddr("_l2MulticallTemplate"), + makeAddr("_l1Weth"), + makeAddr("_l1Multicall"), + 1000 + ); + } + + //// + // Event declarations + //// + event OrbitTokenBridgeCreated( + address indexed inbox, + address indexed owner, + L1DeploymentAddresses l1Deployment, + L2DeploymentAddresses l2Deployment, + address proxyAdmin, + address upgradeExecutor + ); + event OrbitTokenBridgeTemplatesUpdated(); + event OrbitTokenBridgeDeploymentSet( + address indexed inbox, L1DeploymentAddresses l1, L2DeploymentAddresses l2 + ); +} diff --git a/test-foundry/L1OrbitIntegration.t.sol b/test-foundry/L1OrbitIntegration.t.sol index 985856e8c1..c7c1badf48 100644 --- a/test-foundry/L1OrbitIntegration.t.sol +++ b/test-foundry/L1OrbitIntegration.t.sol @@ -50,7 +50,7 @@ contract IntegrationTest is Test { 1_000_000 ether, address(this) ); - inbox = ERC20Inbox(TestUtil.deployProxy(address(new ERC20Inbox()))); + inbox = ERC20Inbox(TestUtil.deployProxy(address(new ERC20Inbox(104857)))); bridge = ERC20Bridge(TestUtil.deployProxy(address(new ERC20Bridge()))); // init bridge and inbox diff --git a/test-foundry/L2AtomicTokenBridgeFactory.t.sol b/test-foundry/L2AtomicTokenBridgeFactory.t.sol new file mode 100644 index 0000000000..b02da64264 --- /dev/null +++ b/test-foundry/L2AtomicTokenBridgeFactory.t.sol @@ -0,0 +1,514 @@ +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.8.0; + +import "forge-std/Test.sol"; +import { + L2AtomicTokenBridgeFactory, + L2RuntimeCode, + ProxyAdmin, + BeaconProxyFactory, + StandardArbERC20, + UpgradeableBeacon, + aeWETH +} from "contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol"; +import {L2GatewayRouter} from "contracts/tokenbridge/arbitrum/gateway/L2GatewayRouter.sol"; +import {L2ERC20Gateway} from "contracts/tokenbridge/arbitrum/gateway/L2ERC20Gateway.sol"; +import {L2CustomGateway} from "contracts/tokenbridge/arbitrum/gateway/L2CustomGateway.sol"; +import {L2WethGateway} from "contracts/tokenbridge/arbitrum/gateway/L2WethGateway.sol"; +import {CreationCodeHelper} from "contracts/tokenbridge/libraries/CreationCodeHelper.sol"; +import {UpgradeExecutor} from "@offchainlabs/upgrade-executor/src/UpgradeExecutor.sol"; +import {ArbMulticall2} from "contracts/rpc-utils/MulticallV2.sol"; +import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; +import {TransparentUpgradeableProxy} from + "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +import "forge-std/console.sol"; + +contract L2AtomicTokenBridgeFactoryTest is Test { + L2AtomicTokenBridgeFactory public l2Factory; + address public deployer = makeAddr("deployer"); + + address public router; + address public standardGateway; + address public customGateway; + address public wethGateway; + address public weth; + address public upgradeExecutor; + address public multicall; + + /// 'deployL2Contracts' inputs + address public l1Router = makeAddr("l1Router"); + address public l1StandardGateway = makeAddr("l1StandardGateway"); + address public l1CustomGateway = makeAddr("l1CustomGateway"); + address public l1WethGateway = makeAddr("l1WethGateway"); + address public l1Weth = makeAddr("l1Weth"); + address public rollupOwner = makeAddr("rollupOwner"); + address public aliasedL1UpgradeExecutor = makeAddr("aliasedL1UpgradeExecutor"); + + L2RuntimeCode public runtimeCode; + + address private constant ADDRESS_DEAD = address(0x000000000000000000000000000000000000dEaD); + + function setUp() public { + l2Factory = new L2AtomicTokenBridgeFactory(); + + // set templates + router = address(new L2GatewayRouter()); + standardGateway = address(new L2ERC20Gateway()); + customGateway = address(new L2CustomGateway()); + wethGateway = address(new L2WethGateway()); + weth = address(new aeWETH()); + upgradeExecutor = address(new UpgradeExecutor()); + multicall = address(new ArbMulticall2()); + + /// bytecode which is sent via retryable + runtimeCode = L2RuntimeCode( + router.code, + standardGateway.code, + customGateway.code, + wethGateway.code, + weth.code, + upgradeExecutor.code, + multicall.code + ); + } + + /* solhint-disable func-name-mixedcase */ + function test_deployL2Contracts_checkRouter() public { + _deployL2Contracts(); + + address expectedProxyAdminAddress = Create2.computeAddress( + keccak256(abi.encodePacked(bytes("L2PA"), block.chainid, address(this))), + keccak256(type(ProxyAdmin).creationCode), + address(l2Factory) + ); + + address expectedL2ERC20GwAddress = _computeAddress( + keccak256(abi.encodePacked(bytes("L2SGW"), block.chainid, address(this))), + expectedProxyAdminAddress + ); + + address expectedL2RouterAddress = _computeAddress( + keccak256(abi.encodePacked(bytes("L2R"), block.chainid, address(this))), + expectedProxyAdminAddress + ); + assertEq( + L2GatewayRouter(expectedL2RouterAddress).counterpartGateway(), + l1Router, + "Wrong l1Router" + ); + assertEq( + L2GatewayRouter(expectedL2RouterAddress).defaultGateway(), + expectedL2ERC20GwAddress, + "Wrong defaultGateway" + ); + + // logic + address expectedL2RouterLogicAddress = Create2.computeAddress( + keccak256(abi.encodePacked(bytes("L2R"), block.chainid, address(this))), + keccak256(CreationCodeHelper.getCreationCodeFor(runtimeCode.router)), + address(l2Factory) + ); + assertEq( + L2GatewayRouter(expectedL2RouterLogicAddress).counterpartGateway(), + ADDRESS_DEAD, + "Wrong l1Router" + ); + assertEq( + L2GatewayRouter(expectedL2RouterLogicAddress).defaultGateway(), + ADDRESS_DEAD, + "Wrong defaultGateway" + ); + } + + function test_deployL2Contracts_checkStandardGateway() public { + _deployL2Contracts(); + + // standard gateway + address expectedProxyAdminAddress = Create2.computeAddress( + keccak256(abi.encodePacked(bytes("L2PA"), block.chainid, address(this))), + keccak256(type(ProxyAdmin).creationCode), + address(l2Factory) + ); + + address expectedL2StandardGwAddress = _computeAddress( + keccak256(abi.encodePacked(bytes("L2SGW"), block.chainid, address(this))), + expectedProxyAdminAddress + ); + + address expectedL2RouterAddress = _computeAddress( + keccak256(abi.encodePacked(bytes("L2R"), block.chainid, address(this))), + expectedProxyAdminAddress + ); + + assertEq( + L2ERC20Gateway(expectedL2StandardGwAddress).counterpartGateway(), + l1StandardGateway, + "Wrong counterpartGateway" + ); + assertEq( + L2ERC20Gateway(expectedL2StandardGwAddress).router(), + expectedL2RouterAddress, + "Wrong router" + ); + + // beacon proxy stuff + address expectedL2BeaconProxyFactoryAddress = Create2.computeAddress( + keccak256(abi.encodePacked(bytes("L2BPF"), block.chainid, address(this))), + keccak256(type(BeaconProxyFactory).creationCode), + address(l2Factory) + ); + assertEq( + L2ERC20Gateway(expectedL2StandardGwAddress).beaconProxyFactory(), + expectedL2BeaconProxyFactoryAddress, + "Wrong beaconProxyFactory" + ); + address expectedStandardArbERC20Address = Create2.computeAddress( + keccak256(abi.encodePacked(bytes("L2BPF"), block.chainid, address(this))), + keccak256(type(StandardArbERC20).creationCode), + address(l2Factory) + ); + address expectedBeaconAddress = Create2.computeAddress( + keccak256(abi.encodePacked(bytes("L2BPF"), block.chainid, address(this))), + keccak256( + abi.encodePacked( + type(UpgradeableBeacon).creationCode, + abi.encode(expectedStandardArbERC20Address) + ) + ), + address(l2Factory) + ); + + assertEq( + UpgradeableBeacon(BeaconProxyFactory(expectedL2BeaconProxyFactoryAddress).beacon()) + .implementation(), + expectedStandardArbERC20Address, + "Wrong implementation" + ); + assertEq( + BeaconProxyFactory(expectedL2BeaconProxyFactoryAddress).beacon(), + expectedBeaconAddress, + "Wrong beacon" + ); + assertEq( + UpgradeableBeacon(expectedBeaconAddress).implementation(), + expectedStandardArbERC20Address, + "Wrong implementation" + ); + + address expectedL2UpgExecutorAddress = _computeAddress( + keccak256(abi.encodePacked(bytes("L2E"), block.chainid, address(this))), + expectedProxyAdminAddress + ); + assertEq( + UpgradeableBeacon(expectedBeaconAddress).owner(), + expectedL2UpgExecutorAddress, + "Wrong beacon owner" + ); + + // logic + address expectedL2StandardGwLogicAddress = Create2.computeAddress( + keccak256(abi.encodePacked(bytes("L2SGW"), block.chainid, address(this))), + keccak256(CreationCodeHelper.getCreationCodeFor(runtimeCode.standardGateway)), + address(l2Factory) + ); + assertEq( + L2ERC20Gateway(expectedL2StandardGwLogicAddress).counterpartGateway(), + ADDRESS_DEAD, + "Wrong counterpartGateway" + ); + assertEq( + L2ERC20Gateway(expectedL2StandardGwLogicAddress).router(), ADDRESS_DEAD, "Wrong router" + ); + assertEq( + L2ERC20Gateway(expectedL2StandardGwLogicAddress).beaconProxyFactory(), + ADDRESS_DEAD, + "Wrong beaconProxyFactory" + ); + } + + function test_deployL2Contracts_checkCustomGateway() public { + _deployL2Contracts(); + + // custom gateway + address expectedProxyAdminAddress = Create2.computeAddress( + keccak256(abi.encodePacked(bytes("L2PA"), block.chainid, address(this))), + keccak256(type(ProxyAdmin).creationCode), + address(l2Factory) + ); + + address expectedL2CustomGwAddress = _computeAddress( + keccak256(abi.encodePacked(bytes("L2CGW"), block.chainid, address(this))), + expectedProxyAdminAddress + ); + + address expectedL2RouterAddress = _computeAddress( + keccak256(abi.encodePacked(bytes("L2R"), block.chainid, address(this))), + expectedProxyAdminAddress + ); + + assertEq( + L2CustomGateway(expectedL2CustomGwAddress).counterpartGateway(), + l1CustomGateway, + "Wrong counterpartGateway" + ); + assertEq( + L2CustomGateway(expectedL2CustomGwAddress).router(), + expectedL2RouterAddress, + "Wrong router" + ); + + // logic + address expectedL2CustomGwLogicAddress = Create2.computeAddress( + keccak256(abi.encodePacked(bytes("L2CGW"), block.chainid, address(this))), + keccak256(CreationCodeHelper.getCreationCodeFor(runtimeCode.customGateway)), + address(l2Factory) + ); + assertEq( + L2CustomGateway(expectedL2CustomGwLogicAddress).counterpartGateway(), + ADDRESS_DEAD, + "Wrong counterpartGateway" + ); + assertEq( + L2CustomGateway(expectedL2CustomGwLogicAddress).router(), ADDRESS_DEAD, "Wrong router" + ); + } + + function test_deployL2Contracts_checkWethGateway() public { + _deployL2Contracts(); + + // weth gateway + address expectedProxyAdminAddress = Create2.computeAddress( + keccak256(abi.encodePacked(bytes("L2PA"), block.chainid, address(this))), + keccak256(type(ProxyAdmin).creationCode), + address(l2Factory) + ); + + address expectedL2WethGwAddress = _computeAddress( + keccak256(abi.encodePacked(bytes("L2WGW"), block.chainid, address(this))), + expectedProxyAdminAddress + ); + + address expectedL2Weth = _computeAddress( + keccak256(abi.encodePacked(bytes("L2W"), block.chainid, address(this))), + expectedProxyAdminAddress + ); + + address expectedL2RouterAddress = _computeAddress( + keccak256(abi.encodePacked(bytes("L2R"), block.chainid, address(this))), + expectedProxyAdminAddress + ); + + assertEq( + L2WethGateway(payable(expectedL2WethGwAddress)).counterpartGateway(), + l1WethGateway, + "Wrong counterpartGateway" + ); + assertEq( + L2WethGateway(payable(expectedL2WethGwAddress)).router(), + expectedL2RouterAddress, + "Wrong router" + ); + assertEq(L2WethGateway(payable(expectedL2WethGwAddress)).l1Weth(), l1Weth, "Wrong l1Weth"); + assertEq( + L2WethGateway(payable(expectedL2WethGwAddress)).l2Weth(), expectedL2Weth, "Wrong l2Weth" + ); + + // wethgateway logic + address expectedL2WethGwLogicAddress = Create2.computeAddress( + keccak256(abi.encodePacked(bytes("L2WGW"), block.chainid, address(this))), + keccak256(CreationCodeHelper.getCreationCodeFor(runtimeCode.wethGateway)), + address(l2Factory) + ); + assertEq( + L2WethGateway(payable(expectedL2WethGwLogicAddress)).counterpartGateway(), + ADDRESS_DEAD, + "Wrong counterpartGateway" + ); + assertEq( + L2WethGateway(payable(expectedL2WethGwLogicAddress)).router(), + ADDRESS_DEAD, + "Wrong router" + ); + assertEq( + L2WethGateway(payable(expectedL2WethGwLogicAddress)).l1Weth(), + ADDRESS_DEAD, + "Wrong l1Weth" + ); + assertEq( + L2WethGateway(payable(expectedL2WethGwLogicAddress)).l2Weth(), + ADDRESS_DEAD, + "Wrong l2Weth" + ); + + // weth + aeWETH l2Weth = aeWETH(payable(expectedL2Weth)); + assertEq(l2Weth.name(), "WETH", "Wrong name"); + assertEq(l2Weth.symbol(), "WETH", "Wrong symbol"); + assertEq(l2Weth.decimals(), 18, "Wrong decimals"); + assertEq(l2Weth.l2Gateway(), expectedL2WethGwAddress, "Wrong l2Gateway"); + assertEq(l2Weth.l1Address(), l1Weth, "Wrong l1Weth"); + + // weth logic + address expectedL2WethLogicAddress = Create2.computeAddress( + keccak256(abi.encodePacked(bytes("L2W"), block.chainid, address(this))), + keccak256(CreationCodeHelper.getCreationCodeFor(runtimeCode.aeWeth)), + address(l2Factory) + ); + aeWETH l2WethLogic = aeWETH(payable(expectedL2WethLogicAddress)); + assertEq(l2WethLogic.name(), "", "Wrong name"); + assertEq(l2WethLogic.symbol(), "", "Wrong symbol"); + assertEq(l2WethLogic.decimals(), 0, "Wrong decimals"); + assertEq(l2WethLogic.l2Gateway(), ADDRESS_DEAD, "Wrong l2Gateway"); + assertEq(l2WethLogic.l1Address(), ADDRESS_DEAD, "Wrong l1Weth"); + } + + function test_deployL2Contracts_checkUpgradeExecutor() public { + _deployL2Contracts(); + + // upgrade executor + address expectedProxyAdminAddress = Create2.computeAddress( + keccak256(abi.encodePacked(bytes("L2PA"), block.chainid, address(this))), + keccak256(type(ProxyAdmin).creationCode), + address(l2Factory) + ); + + address expectedL2UpgExecutorAddress = _computeAddress( + keccak256(abi.encodePacked(bytes("L2E"), block.chainid, address(this))), + expectedProxyAdminAddress + ); + + bytes32 executorRole = UpgradeExecutor(expectedL2UpgExecutorAddress).EXECUTOR_ROLE(); + bytes32 adminRole = UpgradeExecutor(expectedL2UpgExecutorAddress).ADMIN_ROLE(); + + assertEq( + UpgradeExecutor(expectedL2UpgExecutorAddress).hasRole( + executorRole, aliasedL1UpgradeExecutor + ), + true, + "Wrong executor role" + ); + assertEq( + UpgradeExecutor(expectedL2UpgExecutorAddress).hasRole(executorRole, rollupOwner), + true, + "Wrong executor role" + ); + assertEq( + UpgradeExecutor(expectedL2UpgExecutorAddress).hasRole( + adminRole, expectedL2UpgExecutorAddress + ), + true, + "Wrong admin role" + ); + + // logic + address expectedL2UpgExecutorLogicAddress = Create2.computeAddress( + keccak256(abi.encodePacked(bytes("L2E"), block.chainid, address(this))), + keccak256(CreationCodeHelper.getCreationCodeFor(runtimeCode.upgradeExecutor)), + address(l2Factory) + ); + assertEq( + UpgradeExecutor(expectedL2UpgExecutorLogicAddress).hasRole(adminRole, ADDRESS_DEAD), + true, + "Wrong admin role" + ); + } + + function test_deployL2Contracts_checkMulticall() public { + _deployL2Contracts(); + + address expectedMulticallAddress = Create2.computeAddress( + keccak256(abi.encodePacked(bytes("L2MC"), block.chainid, address(this))), + keccak256(type(ArbMulticall2).creationCode), + address(l2Factory) + ); + + assertGt(expectedMulticallAddress.code.length, uint256(0), "Multicall code is empty"); + } + + function test_deployL2Contracts_checkProxyAdmin() public { + _deployL2Contracts(); + + address expectedProxyAdminAddress = Create2.computeAddress( + keccak256(abi.encodePacked(bytes("L2PA"), block.chainid, address(this))), + keccak256(type(ProxyAdmin).creationCode), + address(l2Factory) + ); + + address expectedL2UpgExecutorAddress = _computeAddress( + keccak256(abi.encodePacked(bytes("L2E"), block.chainid, address(this))), + expectedProxyAdminAddress + ); + + assertGt(expectedProxyAdminAddress.code.length, uint256(0), "ProxyAdmin code is empty"); + assertEq( + ProxyAdmin(expectedProxyAdminAddress).owner(), + expectedL2UpgExecutorAddress, + "Wrong owner" + ); + } + + function test_deployL2Contracts_revert_AlreadyExists() public { + _deployL2Contracts(); + + vm.expectRevert( + abi.encodeWithSelector( + L2AtomicTokenBridgeFactory.L2AtomicTokenBridgeFactory_AlreadyExists.selector + ) + ); + l2Factory.deployL2Contracts( + runtimeCode, + l1Router, + l1StandardGateway, + l1CustomGateway, + l1WethGateway, + l1Weth, + makeAddr("l2StandardGatewayCanonicalAddress"), + rollupOwner, + aliasedL1UpgradeExecutor + ); + } + + function _deployL2Contracts() internal { + address l2StandardGatewayCanonicalAddress; + + /// expected L2 standard gateway address needs to be provided to 'deployL2Contracts' call as well + address expectedProxyAdminAddress = Create2.computeAddress( + keccak256(abi.encodePacked(bytes("L2PA"), block.chainid, address(this))), + keccak256(type(ProxyAdmin).creationCode), + address(l2Factory) + ); + address expectedL2ERC20GwAddress = _computeAddress( + keccak256(abi.encodePacked(bytes("L2SGW"), block.chainid, address(this))), + expectedProxyAdminAddress + ); + l2StandardGatewayCanonicalAddress = expectedL2ERC20GwAddress; + + /// do the call + l2Factory.deployL2Contracts( + runtimeCode, + l1Router, + l1StandardGateway, + l1CustomGateway, + l1WethGateway, + l1Weth, + l2StandardGatewayCanonicalAddress, + rollupOwner, + aliasedL1UpgradeExecutor + ); + } + + function _computeAddress(bytes32 salt, address proxyAdmin) internal view returns (address) { + return Create2.computeAddress( + salt, + keccak256( + abi.encodePacked( + type(TransparentUpgradeableProxy).creationCode, + abi.encode(l2Factory, proxyAdmin, bytes("")) + ) + ), + address(l2Factory) + ); + } +} From a459c391050867ee8ace489cc5b1f207db9ea925 Mon Sep 17 00:00:00 2001 From: gzeon Date: Thu, 28 Dec 2023 02:45:34 +0800 Subject: [PATCH 48/48] v1.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dc16eb10c5..f0a78cef5c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@arbitrum/token-bridge-contracts", - "version": "1.1.2", + "version": "1.2.0", "license": "Apache-2.0", "scripts": { "prepublishOnly": "hardhat clean && hardhat compile",