From 5fbaacdaf3046f2a96448c0d9b0d4394c929e0fb Mon Sep 17 00:00:00 2001 From: gzeon Date: Tue, 19 Dec 2023 16:39:02 +0800 Subject: [PATCH 01/23] 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 02/23] 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 03/23] 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 04/23] 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 05/23] 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 06/23] 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 07/23] 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 08/23] 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 09/23] 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 10/23] 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 11/23] 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 12/23] 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 13/23] 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 14/23] 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 15/23] 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 16/23] 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 17/23] 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 18/23] 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 19/23] 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 20/23] 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 21/23] 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 22/23] 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 23/23] 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",