diff --git a/.env-sample b/.env-sample index 9e3413c0b9..c3d66993b0 100644 --- a/.env-sample +++ b/.env-sample @@ -1,4 +1,13 @@ -## Goerli - Deploy L1TokenBridgeCreator -ARB_GOERLI_RPC="" -ARB_GOERLI_DEPLOYER_KEY="" -ORBIT_RPC="" \ No newline at end of file +.env-sample + +## Rollup on top of which token bridge will be created +ROLLUP_ADDRESS="" +ROLLUP_OWNER="" +L1_TOKEN_BRIDGE_CREATOR="" + +## RPC endpoints +BASECHAIN_RPC="" +ORBIT_RPC="" + +## Deployer key used for deploying creator and creating token bridge +BASECHAIN_DEPLOYER_KEY="" diff --git a/.gitignore b/.gitignore index 7974e8b46c..ae5e00a019 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ +.gitignore .env node_modules +.vscode/ #Hardhat files cache diff --git a/contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol b/contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol index e856809484..f0e9555167 100644 --- a/contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol +++ b/contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol @@ -6,6 +6,7 @@ import {L2ERC20Gateway} from "./gateway/L2ERC20Gateway.sol"; import {L2CustomGateway} from "./gateway/L2CustomGateway.sol"; import {L2WethGateway} from "./gateway/L2WethGateway.sol"; import {StandardArbERC20} from "./StandardArbERC20.sol"; +import {IUpgradeExecutor} from "@offchainlabs/upgrade-executor/src/IUpgradeExecutor.sol"; import {BeaconProxyFactory} from "../libraries/ClonableBeaconProxy.sol"; import {aeWETH} from "../libraries/aeWETH.sol"; import {UpgradeableBeacon} from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; @@ -32,24 +33,32 @@ contract L2AtomicTokenBridgeFactory { address l1WethGateway, address l1Weth, address l2StandardGatewayCanonicalAddress, - address rollupOwner + address rollupOwner, + address aliasedL1UpgradeExecutor ) external { - // create proxyAdmin which will be used for all contracts. Revert if canonical deployment already exists - address proxyAdminAddress = Create2.computeAddress( - _getL2Salt(OrbitSalts.L2_PROXY_ADMIN), - keccak256(type(ProxyAdmin).creationCode), - address(this) - ); - if (proxyAdminAddress.code.length > 0) { - revert L2AtomicTokenBridgeFactory_AlreadyExists(); + // Create proxyAdmin which will be used for all contracts. Revert if canonical deployment already exists + { + address proxyAdminAddress = Create2.computeAddress( + _getL2Salt(OrbitSalts.L2_PROXY_ADMIN), + keccak256(type(ProxyAdmin).creationCode), + address(this) + ); + if (proxyAdminAddress.code.length > 0) { + revert L2AtomicTokenBridgeFactory_AlreadyExists(); + } } address proxyAdmin = address(new ProxyAdmin{ salt: _getL2Salt(OrbitSalts.L2_PROXY_ADMIN) }()); - // deploy router/gateways + // deploy router/gateways/executor + address upgradeExecutor = _deployUpgradeExecutor( + l2Code.upgradeExecutor, rollupOwner, proxyAdmin, aliasedL1UpgradeExecutor + ); address router = _deployRouter(l2Code.router, l1Router, l2StandardGatewayCanonicalAddress, proxyAdmin); - _deployStandardGateway(l2Code.standardGateway, l1StandardGateway, router, proxyAdmin); + _deployStandardGateway( + l2Code.standardGateway, l1StandardGateway, router, proxyAdmin, upgradeExecutor + ); _deployCustomGateway(l2Code.customGateway, l1CustomGateway, router, proxyAdmin); // fee token based creator will provide address(0) as WETH is not used in ERC20-based chains @@ -59,8 +68,39 @@ contract L2AtomicTokenBridgeFactory { ); } - // transfer ownership to rollup's owner - ProxyAdmin(proxyAdmin).transferOwnership(rollupOwner); + // deploy multicall + Create2.deploy(0, _getL2Salt(OrbitSalts.L2_MULTICALL), _creationCodeFor(l2Code.multicall)); + + // transfer ownership to L2 upgradeExecutor + ProxyAdmin(proxyAdmin).transferOwnership(upgradeExecutor); + } + + function _deployUpgradeExecutor( + bytes calldata runtimeCode, + address rollupOwner, + address proxyAdmin, + 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) + ); + + // create UpgradeExecutor logic and upgrade to it + address upExecutorLogic = Create2.deploy( + 0, _getL2Salt(OrbitSalts.L2_EXECUTOR_LOGIC), _creationCodeFor(runtimeCode) + ); + ProxyAdmin(proxyAdmin).upgrade( + ITransparentUpgradeableProxy(canonicalUpgradeExecutor), upExecutorLogic + ); + + // init upgrade executor + address[] memory executors = new address[](2); + executors[0] = rollupOwner; + executors[1] = aliasedL1UpgradeExecutor; + IUpgradeExecutor(canonicalUpgradeExecutor).initialize(canonicalUpgradeExecutor, executors); + + return canonicalUpgradeExecutor; } function _deployRouter( @@ -89,7 +129,8 @@ contract L2AtomicTokenBridgeFactory { bytes calldata runtimeCode, address l1StandardGateway, address router, - address proxyAdmin + address proxyAdmin, + address upgradeExecutor ) internal { // canonical L2 standard gateway with dummy logic address canonicalStdGateway = _deploySeedProxy( @@ -122,6 +163,9 @@ contract L2AtomicTokenBridgeFactory { L2ERC20Gateway(canonicalStdGateway).initialize( l1StandardGateway, router, address(beaconProxyFactory) ); + + // make L2 executor the beacon owner + beacon.transferOwnership(upgradeExecutor); } function _deployCustomGateway( @@ -201,7 +245,7 @@ contract L2AtomicTokenBridgeFactory { * permissionless. By making msg.sender part of the salt we know exactly which set of contracts is the "canonical" one, * deployed by L1TokenBridgeRetryableSender via retryable ticket. */ - function _getL2Salt(bytes32 prefix) internal view returns (bytes32) { + function _getL2Salt(bytes memory prefix) internal view returns (bytes32) { return keccak256(abi.encodePacked(prefix, msg.sender)); } @@ -257,31 +301,35 @@ struct L2RuntimeCode { bytes customGateway; bytes wethGateway; bytes aeWeth; + bytes upgradeExecutor; + bytes multicall; } /** * Collection of salts used in CREATE2 deployment of L2 token bridge contracts. */ library OrbitSalts { - bytes32 public constant L1_PROXY_ADMIN = keccak256(bytes("OrbitL1ProxyAdmin")); - bytes32 public constant L1_ROUTER = keccak256(bytes("OrbitL1GatewayRouterProxy")); - bytes32 public constant L1_STANDARD_GATEWAY = keccak256(bytes("OrbitL1StandardGatewayProxy")); - bytes32 public constant L1_CUSTOM_GATEWAY = keccak256(bytes("OrbitL1CustomGatewayProxy")); - bytes32 public constant L1_WETH_GATEWAY = keccak256(bytes("OrbitL1WethGatewayProxy")); - - bytes32 public constant L2_PROXY_ADMIN = keccak256(bytes("OrbitL2ProxyAdmin")); - bytes32 public constant L2_ROUTER_LOGIC = keccak256(bytes("OrbitL2GatewayRouterLogic")); - bytes32 public constant L2_ROUTER = keccak256(bytes("OrbitL2GatewayRouterProxy")); - bytes32 public constant L2_STANDARD_GATEWAY_LOGIC = - keccak256(bytes("OrbitL2StandardGatewayLogic")); - bytes32 public constant L2_STANDARD_GATEWAY = keccak256(bytes("OrbitL2StandardGatewayProxy")); - bytes32 public constant L2_CUSTOM_GATEWAY_LOGIC = keccak256(bytes("OrbitL2CustomGatewayLogic")); - bytes32 public constant L2_CUSTOM_GATEWAY = keccak256(bytes("OrbitL2CustomGatewayProxy")); - bytes32 public constant L2_WETH_GATEWAY_LOGIC = keccak256(bytes("OrbitL2WethGatewayLogic")); - bytes32 public constant L2_WETH_GATEWAY = keccak256(bytes("OrbitL2WethGatewayProxy")); - bytes32 public constant L2_WETH_LOGIC = keccak256(bytes("OrbitL2WETH")); - bytes32 public constant L2_WETH = keccak256(bytes("OrbitL2WETHProxy")); - bytes32 public constant L2_STANDARD_ERC20 = keccak256(bytes("OrbitStandardArbERC20")); - bytes32 public constant UPGRADEABLE_BEACON = keccak256(bytes("OrbitUpgradeableBeacon")); - bytes32 public constant BEACON_PROXY_FACTORY = keccak256(bytes("OrbitBeaconProxyFactory")); + bytes public constant L1_PROXY_ADMIN = bytes("OrbitL1ProxyAdmin"); + bytes public constant L1_ROUTER = bytes("OrbitL1GatewayRouterProxy"); + bytes public constant L1_STANDARD_GATEWAY = bytes("OrbitL1StandardGatewayProxy"); + bytes public constant L1_CUSTOM_GATEWAY = bytes("OrbitL1CustomGatewayProxy"); + 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"); } diff --git a/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol b/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol index a9c0d2fb00..e5d937b1df 100644 --- a/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol +++ b/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.4; import { L1TokenBridgeRetryableSender, - L1Addresses, + L1DeploymentAddresses, RetryableParams, L2TemplateAddresses, IERC20Inbox, @@ -17,9 +17,6 @@ import {L1WethGateway} from "./gateway/L1WethGateway.sol"; import {L1OrbitGatewayRouter} from "./gateway/L1OrbitGatewayRouter.sol"; import {L1OrbitERC20Gateway} from "./gateway/L1OrbitERC20Gateway.sol"; import {L1OrbitCustomGateway} from "./gateway/L1OrbitCustomGateway.sol"; - -import {TransparentUpgradeableProxy} from - "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import { L2AtomicTokenBridgeFactory, CanonicalAddressSeed, @@ -28,14 +25,24 @@ import { ProxyAdmin } from "../arbitrum/L2AtomicTokenBridgeFactory.sol"; import {BytesLib} from "../libraries/BytesLib.sol"; +import { + IUpgradeExecutor, + UpgradeExecutor +} 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 {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; +import {ArbMulticall2} from "../../rpc-utils/MulticallV2.sol"; import {BeaconProxyFactory, ClonableBeaconProxy} from "../libraries/ClonableBeaconProxy.sol"; +import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; import { Initializable, OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {TransparentUpgradeableProxy} from + "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {IAccessControlUpgradeable} from + "@openzeppelin/contracts-upgradeable/access/IAccessControlUpgradeable.sol"; /** * @title Layer1 token bridge creator @@ -48,6 +55,8 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { error L1AtomicTokenBridgeCreator_OnlyRollupOwner(); error L1AtomicTokenBridgeCreator_InvalidRouterAddr(); error L1AtomicTokenBridgeCreator_TemplatesNotSet(); + error L1AtomicTokenBridgeCreator_RollupOwnershipMisconfig(); + error L1AtomicTokenBridgeCreator_ProxyAdminNotFound(); event OrbitTokenBridgeCreated( address indexed inbox, @@ -56,7 +65,8 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { address standardGateway, address customGateway, address wethGateway, - address proxyAdmin + address proxyAdmin, + address upgradeExecutor ); event OrbitTokenBridgeTemplatesUpdated(); event NonCanonicalRouterSet(address indexed inbox, address indexed router); @@ -69,6 +79,7 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { L1OrbitGatewayRouter feeTokenBasedRouterTemplate; L1OrbitERC20Gateway feeTokenBasedStandardGatewayTemplate; L1OrbitCustomGateway feeTokenBasedCustomGatewayTemplate; + IUpgradeExecutor upgradeExecutor; } // non-canonical router registry @@ -92,6 +103,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; @@ -140,6 +152,7 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { address _l2CustomGatewayTemplate, address _l2WethGatewayTemplate, address _l2WethTemplate, + address _l2MulticallTemplate, address _l1Weth, uint256 _gasLimitForL2FactoryDeployment ) external onlyOwner { @@ -151,6 +164,7 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { l2CustomGatewayTemplate = _l2CustomGatewayTemplate; l2WethGatewayTemplate = _l2WethGatewayTemplate; l2WethTemplate = _l2WethTemplate; + l2MulticallTemplate = _l2MulticallTemplate; l1Weth = _l1Weth; @@ -170,39 +184,55 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { * because L1 salts are already used at that point and L1 contracts are already deployed at canonical addresses * for that inbox. */ - function createTokenBridge(address inbox, uint256 maxGasForContracts, uint256 gasPriceBid) - external - payable - { + function createTokenBridge( + address inbox, + address rollupOwner, + uint256 maxGasForContracts, + uint256 gasPriceBid + ) external payable { + // templates have to be in place if (address(l1Templates.routerTemplate) == address(0)) { revert L1AtomicTokenBridgeCreator_TemplatesNotSet(); } - bool isUsingFeeToken = _getFeeToken(inbox) != address(0); + // 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(); + } - // deploy L1 side of token bridge - address owner = _getRollupOwner(inbox); - (address router, address standardGateway, address customGateway, address wethGateway) = - _deployL1Contracts(inbox, owner, isUsingFeeToken); + /// deploy L1 side of token bridge + bool isUsingFeeToken = _getFeeToken(inbox) != address(0); + L1DeploymentAddresses memory l1DeploymentAddresses = + _deployL1Contracts(inbox, rollupOwner, upgradeExecutor, isUsingFeeToken); /// deploy factory and then L2 contracts through L2 factory, using 2 retryables calls if (isUsingFeeToken) { _deployL2Factory(inbox, gasPriceBid, isUsingFeeToken); _deployL2ContractsUsingFeeToken( - router, standardGateway, customGateway, inbox, maxGasForContracts, gasPriceBid + l1DeploymentAddresses, + inbox, + maxGasForContracts, + gasPriceBid, + rollupOwner, + upgradeExecutor ); } else { uint256 valueSpentForFactory = _deployL2Factory(inbox, gasPriceBid, isUsingFeeToken); uint256 fundsRemaining = msg.value - valueSpentForFactory; _deployL2ContractsUsingEth( - router, - standardGateway, - customGateway, - wethGateway, + l1DeploymentAddresses, inbox, maxGasForContracts, gasPriceBid, - fundsRemaining + fundsRemaining, + rollupOwner, + upgradeExecutor ); } } @@ -212,7 +242,7 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { * @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 != _getRollupOwner(inbox)) { + if (msg.sender != IInbox(inbox).bridge().rollup().owner()) { revert L1AtomicTokenBridgeCreator_OnlyRollupOwner(); } if (nonCanonicalRouter == getCanonicalL1RouterAddress(inbox)) { @@ -233,23 +263,23 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { return getCanonicalL1RouterAddress(inbox); } - function _deployL1Contracts(address inbox, address owner, bool isUsingFeeToken) - internal - returns ( - address router, - address standardGateway, - address customGateway, - address wethGateway - ) - { - address proxyAdmin = - address(new ProxyAdmin{ salt: _getL1Salt(OrbitSalts.L1_PROXY_ADMIN, inbox) }()); + function _deployL1Contracts( + address inbox, + address rollupOwner, + address upgradeExecutor, + bool isUsingFeeToken + ) 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(); + } // deploy router address routerTemplate = isUsingFeeToken ? address(l1Templates.feeTokenBasedRouterTemplate) : address(l1Templates.routerTemplate); - router = address( + l1Addresses.router = address( new TransparentUpgradeableProxy{ salt: _getL1Salt(OrbitSalts.L1_ROUTER, inbox) }( routerTemplate, proxyAdmin, @@ -258,21 +288,35 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { ); // deploy and init gateways - standardGateway = _deployL1StandardGateway(proxyAdmin, router, inbox, isUsingFeeToken); - customGateway = _deployL1CustomGateway(proxyAdmin, router, inbox, owner, isUsingFeeToken); - wethGateway = isUsingFeeToken ? address(0) : _deployL1WethGateway(proxyAdmin, router, inbox); + l1Addresses.standardGateway = + _deployL1StandardGateway(proxyAdmin, l1Addresses.router, inbox, isUsingFeeToken); + l1Addresses.customGateway = _deployL1CustomGateway( + proxyAdmin, l1Addresses.router, inbox, upgradeExecutor, isUsingFeeToken + ); + l1Addresses.wethGateway = isUsingFeeToken + ? address(0) + : _deployL1WethGateway(proxyAdmin, l1Addresses.router, inbox); + l1Addresses.weth = isUsingFeeToken ? address(0) : l1Weth; // init router - L1GatewayRouter(router).initialize( - owner, address(standardGateway), address(0), getCanonicalL2RouterAddress(), inbox + L1GatewayRouter(l1Addresses.router).initialize( + upgradeExecutor, + l1Addresses.standardGateway, + address(0), + getCanonicalL2RouterAddress(), + inbox ); - // transfer ownership to owner - ProxyAdmin(proxyAdmin).transferOwnership(owner); - // emit it emit OrbitTokenBridgeCreated( - inbox, owner, router, standardGateway, customGateway, wethGateway, proxyAdmin + inbox, + rollupOwner, + l1Addresses.router, + l1Addresses.standardGateway, + l1Addresses.customGateway, + l1Addresses.wethGateway, + proxyAdmin, + upgradeExecutor ); } @@ -309,7 +353,7 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { address proxyAdmin, address router, address inbox, - address owner, + address upgradeExecutor, bool isUsingFeeToken ) internal returns (address) { address template = isUsingFeeToken @@ -324,7 +368,9 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { ) ); - customGateway.initialize(getCanonicalL2CustomGatewayAddress(), router, inbox, owner); + customGateway.initialize( + getCanonicalL2CustomGatewayAddress(), router, inbox, upgradeExecutor + ); return address(customGateway); } @@ -395,14 +441,13 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { } function _deployL2ContractsUsingEth( - address l1Router, - address l1StandardGateway, - address l1CustomGateway, - address l1WethGateway, + L1DeploymentAddresses memory l1Addresses, address inbox, uint256 maxGas, uint256 gasPriceBid, - uint256 availableFunds + uint256 availableFunds, + address rollupOwner, + address upgradeExecutor ) internal { retryableSender.sendRetryableUsingEth{value: availableFunds}( RetryableParams( @@ -413,22 +458,25 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { l2StandardGatewayTemplate, l2CustomGatewayTemplate, l2WethGatewayTemplate, - l2WethTemplate + l2WethTemplate, + address(l1Templates.upgradeExecutor), + l2MulticallTemplate ), - L1Addresses(l1Router, l1StandardGateway, l1CustomGateway, l1WethGateway, l1Weth), + l1Addresses, getCanonicalL2StandardGatewayAddress(), - _getRollupOwner(inbox), - msg.sender + rollupOwner, + msg.sender, + AddressAliasHelper.applyL1ToL2Alias(upgradeExecutor) ); } function _deployL2ContractsUsingFeeToken( - address l1Router, - address l1StandardGateway, - address l1CustomGateway, + L1DeploymentAddresses memory l1Addresses, address inbox, uint256 maxGas, - uint256 gasPriceBid + uint256 gasPriceBid, + address rollupOwner, + address upgradeExecutor ) internal { // transfer fee tokens to inbox to pay for 2nd retryable address feeToken = _getFeeToken(inbox); @@ -444,11 +492,14 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { l2StandardGatewayTemplate, l2CustomGatewayTemplate, address(0), - address(0) + address(0), + address(l1Templates.upgradeExecutor), + l2MulticallTemplate ), - L1Addresses(l1Router, l1StandardGateway, l1CustomGateway, address(0), address(0)), + l1Addresses, getCanonicalL2StandardGatewayAddress(), - _getRollupOwner(inbox) + rollupOwner, + AddressAliasHelper.applyL1ToL2Alias(upgradeExecutor) ); } @@ -506,6 +557,12 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { _getProxyAddress(_getL2Salt(OrbitSalts.L2_WETH_LOGIC), _getL2Salt(OrbitSalts.L2_WETH)); } + function getCanonicalL2UpgradeExecutorAddress() public view returns (address) { + return _getProxyAddress( + _getL2Salt(OrbitSalts.L2_EXECUTOR_LOGIC), _getL2Salt(OrbitSalts.L2_EXECUTOR) + ); + } + function _getFeeToken(address inbox) internal view returns (address) { address bridge = address(IInbox(inbox).bridge()); @@ -562,10 +619,6 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { return abi.encodePacked(hex"63", uint32(code.length), hex"80600E6000396000F3", code); } - function _getRollupOwner(address inbox) internal view returns (address) { - return IInbox(inbox).bridge().rollup().owner(); - } - /** * L2 contracts are deployed as proxy with dummy seed logic contracts using CREATE2. That enables * us to upfront calculate the expected canonical addresses. @@ -596,7 +649,7 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { * reason we make rollup's inbox address part of the salt. It prevents deploying more than one * token bridge. */ - function _getL1Salt(bytes32 prefix, address inbox) internal pure returns (bytes32) { + function _getL1Salt(bytes memory prefix, address inbox) internal pure returns (bytes32) { return keccak256(abi.encodePacked(prefix, inbox)); } @@ -606,7 +659,7 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { * means `retryableSender`'s alias will be used on L2 side to calculate the salt for deploying * L2 contracts (_getL2Salt function in L2AtomicTokenBridgeFactory). */ - function _getL2Salt(bytes32 prefix) internal view returns (bytes32) { + function _getL2Salt(bytes memory prefix) internal view returns (bytes32) { return keccak256( abi.encodePacked(prefix, AddressAliasHelper.applyL1ToL2Alias(address(retryableSender))) ); @@ -616,3 +669,7 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { interface IERC20Bridge { function nativeToken() external view returns (address); } + +interface IInbox_ProxyAdmin { + function getProxyAdmin() external view returns (address); +} diff --git a/contracts/tokenbridge/ethereum/L1TokenBridgeRetryableSender.sol b/contracts/tokenbridge/ethereum/L1TokenBridgeRetryableSender.sol index 94d42ae6f3..bde3de250b 100644 --- a/contracts/tokenbridge/ethereum/L1TokenBridgeRetryableSender.sol +++ b/contracts/tokenbridge/ethereum/L1TokenBridgeRetryableSender.sol @@ -5,7 +5,6 @@ import {IInbox} from "@arbitrum/nitro-contracts/src/bridge/IInbox.sol"; import { L2AtomicTokenBridgeFactory, L2RuntimeCode, - OrbitSalts, ProxyAdmin } from "../arbitrum/L2AtomicTokenBridgeFactory.sol"; import { @@ -42,10 +41,11 @@ contract L1TokenBridgeRetryableSender is Initializable, OwnableUpgradeable { function sendRetryableUsingEth( RetryableParams calldata retryableParams, L2TemplateAddresses calldata l2, - L1Addresses calldata l1, + L1DeploymentAddresses calldata l1, address l2StandardGatewayAddress, address rollupOwner, - address deployer + address deployer, + address aliasedL1UpgradeExecutor ) external payable onlyOwner { bytes memory data = abi.encodeCall( L2AtomicTokenBridgeFactory.deployL2Contracts, @@ -55,7 +55,9 @@ contract L1TokenBridgeRetryableSender is Initializable, OwnableUpgradeable { l2.standardGatewayTemplate.code, l2.customGatewayTemplate.code, l2.wethGatewayTemplate.code, - l2.wethTemplate.code + l2.wethTemplate.code, + l2.upgradeExecutorTemplate.code, + l2.multicallTemplate.code ), l1.router, l1.standardGateway, @@ -63,7 +65,8 @@ contract L1TokenBridgeRetryableSender is Initializable, OwnableUpgradeable { l1.wethGateway, l1.weth, l2StandardGatewayAddress, - rollupOwner + rollupOwner, + aliasedL1UpgradeExecutor ) ); @@ -87,9 +90,10 @@ contract L1TokenBridgeRetryableSender is Initializable, OwnableUpgradeable { function sendRetryableUsingFeeToken( RetryableParams calldata retryableParams, L2TemplateAddresses calldata l2, - L1Addresses calldata l1, + L1DeploymentAddresses calldata l1, address l2StandardGatewayAddress, - address rollupOwner + address rollupOwner, + address aliasedL1UpgradeExecutor ) external payable onlyOwner { bytes memory data = abi.encodeCall( L2AtomicTokenBridgeFactory.deployL2Contracts, @@ -99,7 +103,9 @@ contract L1TokenBridgeRetryableSender is Initializable, OwnableUpgradeable { l2.standardGatewayTemplate.code, l2.customGatewayTemplate.code, "", - "" + "", + l2.upgradeExecutorTemplate.code, + l2.multicallTemplate.code ), l1.router, l1.standardGateway, @@ -107,7 +113,8 @@ contract L1TokenBridgeRetryableSender is Initializable, OwnableUpgradeable { address(0), address(0), l2StandardGatewayAddress, - rollupOwner + rollupOwner, + aliasedL1UpgradeExecutor ) ); @@ -174,12 +181,14 @@ struct L2TemplateAddresses { address customGatewayTemplate; address wethGatewayTemplate; address wethTemplate; + address upgradeExecutorTemplate; + address multicallTemplate; } /** * L1 side of token bridge addresses */ -struct L1Addresses { +struct L1DeploymentAddresses { address router; address standardGateway; address customGateway; diff --git a/contracts/tokenbridge/ethereum/gateway/L1OrbitERC20Gateway.sol b/contracts/tokenbridge/ethereum/gateway/L1OrbitERC20Gateway.sol index dd18aa4cbb..cccd209508 100644 --- a/contracts/tokenbridge/ethereum/gateway/L1OrbitERC20Gateway.sol +++ b/contracts/tokenbridge/ethereum/gateway/L1OrbitERC20Gateway.sol @@ -129,7 +129,7 @@ contract L1OrbitERC20Gateway is L1ERC20Gateway { /** * @notice get rollup's native token that's used to pay for fees */ - function _getNativeFeeToken() internal returns (address) { + function _getNativeFeeToken() internal view returns (address) { address bridge = address(getBridge(inbox)); return IERC20Bridge(bridge).nativeToken(); } diff --git a/hardhat.config.ts b/hardhat.config.ts index e641a55748..b718396056 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -36,7 +36,7 @@ const config = { }, }, { - version: '0.8.17', + version: '0.8.16', settings: { optimizer: { enabled: true, diff --git a/package.json b/package.json index 6d537319a9..8854504cde 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,8 @@ "dependencies": { "@arbitrum/nitro-contracts": "^1.0.0-beta.8", "@openzeppelin/contracts": "4.8.3", - "@openzeppelin/contracts-upgradeable": "4.8.3" + "@openzeppelin/contracts-upgradeable": "4.8.3", + "@offchainlabs/upgrade-executor": "^1.0.0-beta.2" }, "devDependencies": { "@arbitrum/sdk": "^3.1.3", diff --git a/scripts/atomicTokenBridgeDeployer.ts b/scripts/atomicTokenBridgeDeployer.ts index ed0dc31fec..d1b2753970 100644 --- a/scripts/atomicTokenBridgeDeployer.ts +++ b/scripts/atomicTokenBridgeDeployer.ts @@ -21,7 +21,12 @@ import { IInbox__factory, IERC20Bridge__factory, IERC20__factory, + ArbMulticall2__factory, } from '../build/types' +import { + abi as UpgradeExecutorABI, + bytecode as UpgradeExecutorBytecode, +} from '@offchainlabs/upgrade-executor/build/contracts/src/UpgradeExecutor.sol/UpgradeExecutor.json' import { JsonRpcProvider } from '@ethersproject/providers' import { L1ToL2MessageGasEstimator, @@ -48,7 +53,8 @@ export const createTokenBridge = async ( l1Signer: Signer, l2Provider: ethers.providers.Provider, l1TokenBridgeCreator: L1AtomicTokenBridgeCreator, - rollupAddress: string + rollupAddress: string, + rollupOwnerAddress: string ) => { const gasPrice = await l2Provider.getGasPrice() @@ -74,6 +80,8 @@ export const createTokenBridge = async ( customGateway: L2CustomGateway__factory.bytecode, wethGateway: L2WethGateway__factory.bytecode, aeWeth: AeWETH__factory.bytecode, + upgradeExecutor: UpgradeExecutorBytecode, + multicall: ArbMulticall2__factory.bytecode, } const gasEstimateToDeployContracts = await l2FactoryTemplate.estimateGas.deployL2Contracts( @@ -84,6 +92,7 @@ export const createTokenBridge = async ( ethers.Wallet.createRandom().address, ethers.Wallet.createRandom().address, ethers.Wallet.createRandom().address, + ethers.Wallet.createRandom().address, ethers.Wallet.createRandom().address ) const maxGasForContracts = gasEstimateToDeployContracts.mul(2) @@ -117,6 +126,7 @@ export const createTokenBridge = async ( const receipt = await ( await l1TokenBridgeCreator.createTokenBridge( inbox, + rollupOwnerAddress, maxGasForContracts, gasPrice, { value: retryableFee } @@ -301,6 +311,13 @@ export const deployL1TokenBridgeCreator = async ( await new L1OrbitCustomGateway__factory(l1Deployer).deploy() await feeTokenBasedCustomGatewayTemplate.deployed() + const upgradeExecutorFactory = new ethers.ContractFactory( + UpgradeExecutorABI, + UpgradeExecutorBytecode, + l1Deployer + ) + const upgradeExecutor = await upgradeExecutorFactory.deploy() + const l1Templates = { routerTemplate: routerTemplate.address, standardGatewayTemplate: standardGatewayTemplate.address, @@ -311,6 +328,7 @@ export const deployL1TokenBridgeCreator = async ( feeTokenBasedStandardGatewayTemplate.address, feeTokenBasedCustomGatewayTemplate: feeTokenBasedCustomGatewayTemplate.address, + upgradeExecutor: upgradeExecutor.address, } /// deploy L2 contracts as placeholders on L1 @@ -342,6 +360,11 @@ export const deployL1TokenBridgeCreator = async ( const l2WethAddressOnL1 = await new AeWETH__factory(l1Deployer).deploy() await l2WethAddressOnL1.deployed() + const l2MulticallAddressOnL1 = await new ArbMulticall2__factory( + l1Deployer + ).deploy() + await l2MulticallAddressOnL1.deployed() + //// run retryable estimate for deploying L2 factory const deployFactoryGasParams = await getEstimateForDeployingFactory( l1Deployer, @@ -357,6 +380,7 @@ export const deployL1TokenBridgeCreator = async ( l2CustomGatewayAddressOnL1.address, l2WethGatewayAddressOnL1.address, l2WethAddressOnL1.address, + l2MulticallAddressOnL1.address, l1WethAddress, deployFactoryGasParams.gasLimit ) diff --git a/scripts/goerli-deployment/createTokenBridge.ts b/scripts/goerli-deployment/createTokenBridge.ts index 830cea8f88..b2a6039300 100644 --- a/scripts/goerli-deployment/createTokenBridge.ts +++ b/scripts/goerli-deployment/createTokenBridge.ts @@ -5,17 +5,19 @@ import { createTokenBridge, getSigner } from '../atomicTokenBridgeDeployer' import dotenv from 'dotenv' import { L1AtomicTokenBridgeCreator__factory } from '../../build/types' import * as fs from 'fs' +import { env } from 'process' dotenv.config() export const envVars = { - baseChainRpc: process.env['ARB_GOERLI_RPC'] as string, - baseChainDeployerKey: process.env['ARB_GOERLI_DEPLOYER_KEY'] as string, + rollupAddress: process.env['ROLLUP_ADDRESS'] as string, + rollupOwner: process.env['ROLLUP_OWNER'] as string, + l1TokenBridgeCreator: process.env['L1_TOKEN_BRIDGE_CREATOR'] as string, + baseChainRpc: process.env['BASECHAIN_RPC'] as string, + baseChainDeployerKey: process.env['BASECHAIN_DEPLOYER_KEY'] as string, childChainRpc: process.env['ORBIT_RPC'] as string, } -const L1_TOKEN_BRIDGE_CREATOR = '0x4Ba3aC2a2fEf26eAA6d05D71B79fde08Cb3078a9' - /** * Steps: * - read network info from local container and register networks @@ -29,14 +31,22 @@ const L1_TOKEN_BRIDGE_CREATOR = '0x4Ba3aC2a2fEf26eAA6d05D71B79fde08Cb3078a9' * @param l2Url * @returns */ -export const createTokenBridgeOnGoerli = async (rollupAddress: string) => { +export const createTokenBridgeOnGoerli = async () => { + if (envVars.rollupAddress == undefined) + throw new Error('Missing ROLLUP_ADDRESS in env vars') + if (envVars.rollupOwner == undefined) + throw new Error('Missing ROLLUP_OWNER in env vars') + if (envVars.l1TokenBridgeCreator == undefined) + throw new Error('Missing L1_TOKEN_BRIDGE_CREATOR in env vars') if (envVars.baseChainRpc == undefined) - throw new Error('Missing ARB_GOERLI_RPC in env vars') + throw new Error('Missing BASECHAIN_RPC in env vars') if (envVars.baseChainDeployerKey == undefined) - throw new Error('Missing ARB_GOERLI_DEPLOYER_KEY in env vars') + throw new Error('Missing BASECHAIN_DEPLOYER_KEY in env vars') if (envVars.childChainRpc == undefined) throw new Error('Missing ORBIT_RPC in env vars') + console.log('Creating token bridge for rollup', envVars.rollupAddress) + const l1Provider = new JsonRpcProvider(envVars.baseChainRpc) const l1Deployer = getSigner(l1Provider, envVars.baseChainDeployerKey) const l2Provider = new JsonRpcProvider(envVars.childChainRpc) @@ -44,11 +54,11 @@ export const createTokenBridgeOnGoerli = async (rollupAddress: string) => { const { l1Network, l2Network: corel2Network } = await registerGoerliNetworks( l1Provider, l2Provider, - rollupAddress + envVars.rollupAddress ) const l1TokenBridgeCreator = L1AtomicTokenBridgeCreator__factory.connect( - L1_TOKEN_BRIDGE_CREATOR, + envVars.l1TokenBridgeCreator, l1Deployer ) @@ -57,7 +67,8 @@ export const createTokenBridgeOnGoerli = async (rollupAddress: string) => { l1Deployer, l2Provider, l1TokenBridgeCreator, - rollupAddress + envVars.rollupAddress, + envVars.rollupOwner ) const l2Network = { @@ -159,20 +170,7 @@ const registerGoerliNetworks = async ( } async function main() { - const args = process.argv.slice(2) - if (args.length != 1) { - console.log( - "Please provide exactly 1 argument - rollup address.\nIe. `yarn run create:goerli:token-bridge -- '0xDAB64b6E86035Aa9EB697341B663fb4B46930E60'`" - ) - return - } - - const rollupAddress = args[0] - console.log('Creating token bridge for rollup', rollupAddress) - - const { l1Network, l2Network } = await createTokenBridgeOnGoerli( - rollupAddress - ) + const { l1Network, l2Network } = await createTokenBridgeOnGoerli() const NETWORK_FILE = 'network.json' fs.writeFileSync( NETWORK_FILE, diff --git a/scripts/goerli-deployment/deployTokenBridgeCreator.ts b/scripts/goerli-deployment/deployTokenBridgeCreator.ts index 1c0498ba80..e28493dcb4 100644 --- a/scripts/goerli-deployment/deployTokenBridgeCreator.ts +++ b/scripts/goerli-deployment/deployTokenBridgeCreator.ts @@ -10,8 +10,8 @@ import dotenv from 'dotenv' dotenv.config() export const envVars = { - baseChainRpc: process.env['ARB_GOERLI_RPC'] as string, - baseChainDeployerKey: process.env['ARB_GOERLI_DEPLOYER_KEY'] as string, + baseChainRpc: process.env['BASECHAIN_RPC'] as string, + baseChainDeployerKey: process.env['BASECHAIN_DEPLOYER_KEY'] as string, childChainRpc: process.env['ORBIT_RPC'] as string, } @@ -32,9 +32,9 @@ const ARB_GOERLI_WETH = '0xEe01c0CD76354C383B8c7B4e65EA88D00B06f36f' */ export const deployTokenBridgeCreator = async (rollupAddress: string) => { if (envVars.baseChainRpc == undefined) - throw new Error('Missing ARB_GOERLI_RPC in env vars') + throw new Error('Missing BASECHAIN_RPC in env vars') if (envVars.baseChainDeployerKey == undefined) - throw new Error('Missing ARB_GOERLI_DEPLOYER_KEY in env vars') + throw new Error('Missing BASECHAIN_DEPLOYER_KEY in env vars') if (envVars.childChainRpc == undefined) throw new Error('Missing ORBIT_RPC in env vars') @@ -127,7 +127,7 @@ const registerGoerliNetworks = async ( async function main() { // this is just random Orbit rollup that will be used to estimate gas needed to deploy L2 token bridge factory via retryable - const rollupAddress = '0xDAB64b6E86035Aa9EB697341B663fb4B46930E60' + const rollupAddress = '0xe16E44efb06F33ed700618A738E24FeFd7801A84' const l1TokenBridgeCreator = await deployTokenBridgeCreator(rollupAddress) console.log('L1TokenBridgeCreator:', l1TokenBridgeCreator.address) } diff --git a/scripts/local-deployment/deployCreatorAndCreateTokenBridge.ts b/scripts/local-deployment/deployCreatorAndCreateTokenBridge.ts index 41af973f43..2dbac143c5 100644 --- a/scripts/local-deployment/deployCreatorAndCreateTokenBridge.ts +++ b/scripts/local-deployment/deployCreatorAndCreateTokenBridge.ts @@ -86,7 +86,8 @@ export const setupTokenBridgeInLocalEnv = async () => { l1Deployer, l2Deployer.provider!, l1TokenBridgeCreator, - coreL2Network.ethBridge.rollup + coreL2Network.ethBridge.rollup, + l1Deployer.address ) const l2Network: L2Network = { diff --git a/test-e2e/tokenBridgeDeploymentTest.ts b/test-e2e/tokenBridgeDeploymentTest.ts index 395f68aa4f..972cc3ced2 100644 --- a/test-e2e/tokenBridgeDeploymentTest.ts +++ b/test-e2e/tokenBridgeDeploymentTest.ts @@ -1,11 +1,10 @@ -import { L1Network, L2Network, getL1Network, getL2Network } from '@arbitrum/sdk' -import { JsonRpcProvider } from '@ethersproject/providers' +import { JsonRpcProvider, Provider, Filter } from '@ethersproject/providers' import { BeaconProxyFactory__factory, IERC20Bridge__factory, IInbox__factory, - IOwnable, IOwnable__factory, + L1AtomicTokenBridgeCreator__factory, L1CustomGateway, L1CustomGateway__factory, L1ERC20Gateway, @@ -22,179 +21,166 @@ import { L2GatewayRouter__factory, L2WethGateway, L2WethGateway__factory, - ProxyAdmin, - ProxyAdmin__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' +import { applyAlias } from '../test/testhelper' import path from 'path' import fs from 'fs' -import { - addCustomNetwork, - l1Networks, - l2Networks, -} from '@arbitrum/sdk/dist/lib/dataEntities/networks' import { expect } from 'chai' import { ethers } from 'hardhat' +import { Contract } from 'ethers' const config = { + l1Url: process.env.BASECHAIN_RPC || 'http://localhost:8545', l2Url: process.env.ORBIT_RPC || 'http://localhost:8547', - l1Url: process.env.ARB_GOERLI_RPC || 'http://localhost:8545', } -let _l1Network: L1Network -let _l2Network: L2Network - -let _l1Provider: JsonRpcProvider -let _l2Provider: JsonRpcProvider +let l1Provider: JsonRpcProvider +let l2Provider: JsonRpcProvider describe('tokenBridge', () => { it('should have deployed and initialized token bridge contracts', async function () { - const { l1Network, l1Provider, l2Network, l2Provider } = - await getProvidersAndSetupNetworks({ - l1Url: config.l1Url, - l2Url: config.l2Url, - networkFilename: './network.json', - }) - - _l1Network = l1Network - _l2Network = l2Network + l1Provider = new JsonRpcProvider(config.l1Url) + l2Provider = new JsonRpcProvider(config.l2Url) + + /// get rollup and L1 creator addresses as entrypoint, either from env vars or from network.json + let rollupAddress: string + let l1TokenBridgeCreator: string + if (process.env.ROLLUP_ADDRESS && process.env.L1_TOKEN_BRIDGE_CREATOR) { + rollupAddress = process.env.ROLLUP_ADDRESS as string + l1TokenBridgeCreator = process.env.L1_TOKEN_BRIDGE_CREATOR as string + } else { + const localNetworkFile = path.join(__dirname, '..', 'network.json') + if (fs.existsSync(localNetworkFile)) { + const data = JSON.parse(fs.readFileSync(localNetworkFile).toString()) + rollupAddress = data['l2Network']['ethBridge']['rollup'] + l1TokenBridgeCreator = data['l1TokenBridgeCreator'] + } else { + throw new Error( + "Can't find rollup address info. Either set ROLLUP_ADDRESS env var or provide network.json file" + ) + } + } - _l1Provider = l1Provider - _l2Provider = l2Provider + /// get addresses + const { l1, l2 } = await _getTokenBridgeAddresses( + rollupAddress, + l1TokenBridgeCreator + ) //// L1 checks await checkL1RouterInitialization( - L1GatewayRouter__factory.connect( - _l2Network.tokenBridge.l1GatewayRouter, - l1Provider - ) + L1GatewayRouter__factory.connect(l1.router, l1Provider), + l1, + l2 ) await checkL1StandardGatewayInitialization( - L1ERC20Gateway__factory.connect( - _l2Network.tokenBridge.l1ERC20Gateway, - l1Provider - ) + L1ERC20Gateway__factory.connect(l1.standardGateway, l1Provider), + l1, + l2 ) await checkL1CustomGatewayInitialization( - L1CustomGateway__factory.connect( - _l2Network.tokenBridge.l1CustomGateway, - l1Provider - ) + L1CustomGateway__factory.connect(l1.customGateway, l1Provider), + l1, + l2 ) - const usingFeeToken = await isUsingFeeToken( - _l2Network.ethBridge.inbox, - l1Provider - ) + const usingFeeToken = await isUsingFeeToken(l1.inbox, l1Provider) if (!usingFeeToken) await checkL1WethGatewayInitialization( - L1WethGateway__factory.connect( - _l2Network.tokenBridge.l1WethGateway, - l1Provider - ) + L1WethGateway__factory.connect(l1.wethGateway, l1Provider), + l1, + l2 ) //// L2 checks await checkL2RouterInitialization( - L2GatewayRouter__factory.connect( - _l2Network.tokenBridge.l2GatewayRouter, - l2Provider - ) + L2GatewayRouter__factory.connect(l2.router, l2Provider), + l1, + l2 ) await checkL2StandardGatewayInitialization( - L2ERC20Gateway__factory.connect( - _l2Network.tokenBridge.l2ERC20Gateway, - l2Provider - ) + L2ERC20Gateway__factory.connect(l2.standardGateway, l2Provider), + l1, + l2 ) await checkL2CustomGatewayInitialization( - L2CustomGateway__factory.connect( - _l2Network.tokenBridge.l2CustomGateway, - l2Provider - ) - ) - - const rollupOwner = await IOwnable__factory.connect( - _l2Network.ethBridge.rollup, - l1Provider - ).owner() - await checkOwnership( - rollupOwner.toLowerCase(), - ProxyAdmin__factory.connect( - _l2Network.tokenBridge.l1ProxyAdmin, - l1Provider - ), - ProxyAdmin__factory.connect( - _l2Network.tokenBridge.l2ProxyAdmin, - l2Provider - ), - L1GatewayRouter__factory.connect( - _l2Network.tokenBridge.l1GatewayRouter, - l1Provider - ), - L1CustomGateway__factory.connect( - _l2Network.tokenBridge.l1CustomGateway, - l1Provider - ) + L2CustomGateway__factory.connect(l2.customGateway, l2Provider), + l1, + l2 ) if (!usingFeeToken) { await checkL2WethGatewayInitialization( - L2WethGateway__factory.connect( - _l2Network.tokenBridge.l2WethGateway, - l2Provider - ) + L2WethGateway__factory.connect(l2.wethGateway, l2Provider), + l1, + l2 ) } + + const upgExecutor = new ethers.Contract( + l2.upgradeExecutor, + UpgradeExecutorABI, + l2Provider + ) + await checkL2UpgradeExecutorInitialization(upgExecutor, l1) + + await checkL1Ownership(l1) + await checkL2Ownership(l2) }) }) //// L1 contracts -async function checkL1RouterInitialization(l1Router: L1GatewayRouter) { +async function checkL1RouterInitialization( + l1Router: L1GatewayRouter, + l1: L1, + l2: L2 +) { console.log('checkL1RouterInitialization') expect((await l1Router.defaultGateway()).toLowerCase()).to.be.eq( - _l2Network.tokenBridge.l1ERC20Gateway.toLowerCase() + l1.standardGateway.toLowerCase() ) - expect((await l1Router.inbox()).toLowerCase()).to.be.eq( - _l2Network.ethBridge.inbox.toLowerCase() + l1.inbox.toLowerCase() ) - expect((await l1Router.router()).toLowerCase()).to.be.eq( ethers.constants.AddressZero ) - expect((await l1Router.counterpartGateway()).toLowerCase()).to.be.eq( - _l2Network.tokenBridge.l2GatewayRouter.toLowerCase() + l2.router.toLowerCase() ) } async function checkL1StandardGatewayInitialization( - l1ERC20Gateway: L1ERC20Gateway + l1ERC20Gateway: L1ERC20Gateway, + l1: L1, + l2: L2 ) { console.log('checkL1StandardGatewayInitialization') expect((await l1ERC20Gateway.counterpartGateway()).toLowerCase()).to.be.eq( - _l2Network.tokenBridge.l2ERC20Gateway.toLowerCase() + l2.standardGateway.toLowerCase() ) expect((await l1ERC20Gateway.router()).toLowerCase()).to.be.eq( - _l2Network.tokenBridge.l1GatewayRouter.toLowerCase() + l1.router.toLowerCase() ) expect((await l1ERC20Gateway.inbox()).toLowerCase()).to.be.eq( - _l2Network.ethBridge.inbox.toLowerCase() + l1.inbox.toLowerCase() ) expect((await l1ERC20Gateway.l2BeaconProxyFactory()).toLowerCase()).to.be.eq( ( await L2ERC20Gateway__factory.connect( await l1ERC20Gateway.counterpartGateway(), - _l2Provider + l2Provider ).beaconProxyFactory() ).toLowerCase() ) @@ -202,7 +188,7 @@ async function checkL1StandardGatewayInitialization( ( await BeaconProxyFactory__factory.connect( await l1ERC20Gateway.l2BeaconProxyFactory(), - _l2Provider + l2Provider ).cloneableProxyHash() ).toLowerCase() ) @@ -212,61 +198,84 @@ async function checkL1StandardGatewayInitialization( } async function checkL1CustomGatewayInitialization( - l1CustomGateway: L1CustomGateway + l1CustomGateway: L1CustomGateway, + l1: L1, + l2: L2 ) { console.log('checkL1CustomGatewayInitialization') expect((await l1CustomGateway.counterpartGateway()).toLowerCase()).to.be.eq( - _l2Network.tokenBridge.l2CustomGateway.toLowerCase() + l2.customGateway.toLowerCase() ) expect((await l1CustomGateway.router()).toLowerCase()).to.be.eq( - _l2Network.tokenBridge.l1GatewayRouter.toLowerCase() + l1.router.toLowerCase() ) expect((await l1CustomGateway.inbox()).toLowerCase()).to.be.eq( - _l2Network.ethBridge.inbox.toLowerCase() + l1.inbox.toLowerCase() ) - // TODO - // owner check - expect((await l1CustomGateway.whitelist()).toLowerCase()).to.be.eq( ethers.constants.AddressZero ) } -async function checkL1WethGatewayInitialization(l1WethGateway: L1WethGateway) { +async function checkL1WethGatewayInitialization( + l1WethGateway: L1WethGateway, + l1: L1, + l2: L2 +) { console.log('checkL1WethGatewayInitialization') expect((await l1WethGateway.counterpartGateway()).toLowerCase()).to.be.eq( - _l2Network.tokenBridge.l2WethGateway.toLowerCase() + l2.wethGateway.toLowerCase() ) expect((await l1WethGateway.router()).toLowerCase()).to.be.eq( - _l2Network.tokenBridge.l1GatewayRouter.toLowerCase() + l1.router.toLowerCase() ) expect((await l1WethGateway.inbox()).toLowerCase()).to.be.eq( - _l2Network.ethBridge.inbox.toLowerCase() + l1.inbox.toLowerCase() ) - expect((await l1WethGateway.l1Weth()).toLowerCase()).to.be.eq( - _l2Network.tokenBridge.l1Weth.toLowerCase() + expect((await l1WethGateway.l1Weth()).toLowerCase()).to.not.be.eq( + ethers.constants.AddressZero ) - expect((await l1WethGateway.l2Weth()).toLowerCase()).to.be.eq( - _l2Network.tokenBridge.l2Weth.toLowerCase() + expect((await l1WethGateway.l2Weth()).toLowerCase()).to.not.be.eq( + ethers.constants.AddressZero ) } +async function checkL2UpgradeExecutorInitialization( + l2Executor: Contract, + l1: L1 +) { + console.log('checkL2UpgradeExecutorInitialization') + + //// check assigned/revoked roles are correctly set + const adminRole = await l2Executor.ADMIN_ROLE() + 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, aliasedL1Executor)).to.be.true +} + //// L2 contracts -async function checkL2RouterInitialization(l2Router: L2GatewayRouter) { +async function checkL2RouterInitialization( + l2Router: L2GatewayRouter, + l1: L1, + l2: L2 +) { console.log('checkL2RouterInitialization') expect((await l2Router.defaultGateway()).toLowerCase()).to.be.eq( - _l2Network.tokenBridge.l2ERC20Gateway.toLowerCase() + l2.standardGateway.toLowerCase() ) expect((await l2Router.router()).toLowerCase()).to.be.eq( @@ -274,28 +283,30 @@ async function checkL2RouterInitialization(l2Router: L2GatewayRouter) { ) expect((await l2Router.counterpartGateway()).toLowerCase()).to.be.eq( - _l2Network.tokenBridge.l1GatewayRouter.toLowerCase() + l1.router.toLowerCase() ) } async function checkL2StandardGatewayInitialization( - l2ERC20Gateway: L2ERC20Gateway + l2ERC20Gateway: L2ERC20Gateway, + l1: L1, + l2: L2 ) { console.log('checkL2StandardGatewayInitialization') expect((await l2ERC20Gateway.counterpartGateway()).toLowerCase()).to.be.eq( - _l2Network.tokenBridge.l1ERC20Gateway.toLowerCase() + l1.standardGateway.toLowerCase() ) expect((await l2ERC20Gateway.router()).toLowerCase()).to.be.eq( - _l2Network.tokenBridge.l2GatewayRouter.toLowerCase() + l2.router.toLowerCase() ) expect((await l2ERC20Gateway.beaconProxyFactory()).toLowerCase()).to.be.eq( ( await L1ERC20Gateway__factory.connect( await l2ERC20Gateway.counterpartGateway(), - _l1Provider + l1Provider ).l2BeaconProxyFactory() ).toLowerCase() ) @@ -304,116 +315,110 @@ async function checkL2StandardGatewayInitialization( ( await L1ERC20Gateway__factory.connect( await l2ERC20Gateway.counterpartGateway(), - _l1Provider + l1Provider ).cloneableProxyHash() ).toLowerCase() ) } async function checkL2CustomGatewayInitialization( - l2CustomGateway: L2CustomGateway + l2CustomGateway: L2CustomGateway, + l1: L1, + l2: L2 ) { console.log('checkL2CustomGatewayInitialization') expect((await l2CustomGateway.counterpartGateway()).toLowerCase()).to.be.eq( - _l2Network.tokenBridge.l1CustomGateway.toLowerCase() + l1.customGateway.toLowerCase() ) expect((await l2CustomGateway.router()).toLowerCase()).to.be.eq( - _l2Network.tokenBridge.l2GatewayRouter.toLowerCase() + l2.router.toLowerCase() ) } -async function checkL2WethGatewayInitialization(l2WethGateway: L2WethGateway) { +async function checkL2WethGatewayInitialization( + l2WethGateway: L2WethGateway, + l1: L1, + l2: L2 +) { console.log('checkL2WethGatewayInitialization') expect((await l2WethGateway.counterpartGateway()).toLowerCase()).to.be.eq( - _l2Network.tokenBridge.l1WethGateway.toLowerCase() + l1.wethGateway.toLowerCase() ) expect((await l2WethGateway.router()).toLowerCase()).to.be.eq( - _l2Network.tokenBridge.l2GatewayRouter.toLowerCase() + l2.router.toLowerCase() ) - expect((await l2WethGateway.l1Weth()).toLowerCase()).to.be.eq( - _l2Network.tokenBridge.l1Weth.toLowerCase() + expect((await l2WethGateway.l1Weth()).toLowerCase()).to.not.be.eq( + ethers.constants.AddressZero ) - expect((await l2WethGateway.l2Weth()).toLowerCase()).to.be.eq( - _l2Network.tokenBridge.l2Weth.toLowerCase() + expect((await l2WethGateway.l2Weth()).toLowerCase()).to.not.be.eq( + ethers.constants.AddressZero ) } -async function checkOwnership( - rollupOwner: string, - l1ProxyAdmin: ProxyAdmin, - l2ProxyAdmin: ProxyAdmin, - l1Router: L1GatewayRouter, - l1CustomGateway: L1CustomGateway -) { - console.log('checkL2ProxyAdminInitialization') +async function checkL1Ownership(l1: L1) { + console.log('checkL1Ownership') + + // check proxyAdmins + expect(await _getProxyAdmin(l1.router, l1Provider)).to.be.eq(l1.proxyAdmin) + expect(await _getProxyAdmin(l1.standardGateway, l1Provider)).to.be.eq( + l1.proxyAdmin + ) + expect(await _getProxyAdmin(l1.customGateway, l1Provider)).to.be.eq( + l1.proxyAdmin + ) + if (l1.wethGateway !== ethers.constants.AddressZero) { + expect(await _getProxyAdmin(l1.wethGateway, l1Provider)).to.be.eq( + l1.proxyAdmin + ) + } + expect(await _getProxyAdmin(l1.upgradeExecutor, l1Provider)).to.be.eq( + l1.proxyAdmin + ) - expect(rollupOwner).to.be.eq((await l1ProxyAdmin.owner()).toLowerCase()) - expect(rollupOwner).to.be.eq((await l2ProxyAdmin.owner()).toLowerCase()) - expect(rollupOwner).to.be.eq((await l1Router.owner()).toLowerCase()) - expect(rollupOwner).to.be.eq((await l1CustomGateway.owner()).toLowerCase()) + // check ownables + expect(await _getOwner(l1.proxyAdmin, l1Provider)).to.be.eq( + l1.upgradeExecutor + ) + expect(await _getOwner(l1.router, l1Provider)).to.be.eq(l1.upgradeExecutor) + expect(await _getOwner(l1.customGateway, l1Provider)).to.be.eq( + l1.upgradeExecutor + ) } -export const getProvidersAndSetupNetworks = async (setupConfig: { - l1Url: string - l2Url: string - networkFilename?: string -}): Promise<{ - l1Network: L1Network - l2Network: L2Network - l1Provider: JsonRpcProvider - l2Provider: JsonRpcProvider -}> => { - const l1Provider = new JsonRpcProvider(setupConfig.l1Url) - const l2Provider = new JsonRpcProvider(setupConfig.l2Url) - - if (setupConfig.networkFilename) { - // check if theres an existing network available - const localNetworkFile = path.join( - __dirname, - '..', - setupConfig.networkFilename - ) - if (fs.existsSync(localNetworkFile)) { - const { l1Network, l2Network } = JSON.parse( - fs.readFileSync(localNetworkFile).toString() - ) as { - l1Network: L1Network - l2Network: L2Network - } +async function checkL2Ownership(l2: L2) { + console.log('checkL2Ownership') - const existingL1Network = l1Networks[l1Network.chainID.toString()] - const existingL2Network = l2Networks[l2Network.chainID.toString()] - if (!existingL2Network) { - addCustomNetwork({ - // dont add the l1 network if it's already been added - customL1Network: existingL1Network ? undefined : l1Network, - customL2Network: l2Network, - }) - } + const l2ProxyAdmin = await _getProxyAdmin(l2.router, l2Provider) - return { - l1Network, - l1Provider, - l2Network, - l2Provider, - } - } else throw Error(`Missing file ${localNetworkFile}`) - } else { - return { - l1Network: await getL1Network(l1Provider), - l1Provider, - l2Network: await getL2Network(l2Provider), - l2Provider, - } + // check proxyAdmins + expect(await _getProxyAdmin(l2.router, l2Provider)).to.be.eq(l2ProxyAdmin) + expect(await _getProxyAdmin(l2.standardGateway, l2Provider)).to.be.eq( + l2ProxyAdmin + ) + expect(await _getProxyAdmin(l2.customGateway, l2Provider)).to.be.eq( + l2ProxyAdmin + ) + + if (l2.wethGateway != ethers.constants.AddressZero) { + expect(await _getProxyAdmin(l2.wethGateway, l2Provider)).to.be.eq( + l2ProxyAdmin + ) } + expect(await _getProxyAdmin(l2.upgradeExecutor, l2Provider)).to.be.eq( + l2ProxyAdmin + ) + + // check ownables + expect(await _getOwner(l2ProxyAdmin, l2Provider)).to.be.eq(l2.upgradeExecutor) } +//// utils async function isUsingFeeToken(inbox: string, l1Provider: JsonRpcProvider) { const bridge = await IInbox__factory.connect(inbox, l1Provider).bridge() @@ -425,3 +430,164 @@ async function isUsingFeeToken(inbox: string, l1Provider: JsonRpcProvider) { return true } + +async function _getTokenBridgeAddresses( + 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', + }) + + if (logs.length === 0) { + throw new Error( + "Couldn't find any OrbitTokenBridgeCreated events in block range[" + + fromBlock + + ',latest]' + ) + } + + const logData = l1TokenBridgeCreator.interface.parseLog(logs[0]) + + const { + inbox, + owner, + router, + standardGateway, + customGateway, + wethGateway, + proxyAdmin, + upgradeExecutor, + } = logData.args + const l1 = { + inbox: inbox.toLowerCase(), + rollupOwner: owner.toLowerCase(), + router: router.toLowerCase(), + standardGateway: standardGateway.toLowerCase(), + customGateway: customGateway.toLowerCase(), + wethGateway: wethGateway.toLowerCase(), + proxyAdmin: proxyAdmin.toLowerCase(), + upgradeExecutor: upgradeExecutor.toLowerCase(), + } + + const usingFeeToken = await isUsingFeeToken(l1.inbox, l1Provider) + + //// L2 + const l2 = { + router: ( + await l1TokenBridgeCreator.getCanonicalL2RouterAddress() + ).toLowerCase(), + standardGateway: ( + await l1TokenBridgeCreator.getCanonicalL2StandardGatewayAddress() + ).toLowerCase(), + customGateway: ( + await l1TokenBridgeCreator.getCanonicalL2CustomGatewayAddress() + ).toLowerCase(), + wethGateway: (usingFeeToken + ? ethers.constants.AddressZero + : await l1TokenBridgeCreator.getCanonicalL2WethGatewayAddress() + ).toLowerCase(), + weth: (usingFeeToken + ? ethers.constants.AddressZero + : await l1TokenBridgeCreator.getCanonicalL2WethAddress() + ).toLowerCase(), + upgradeExecutor: ( + await l1TokenBridgeCreator.getCanonicalL2UpgradeExecutorAddress() + ).toLowerCase(), + } + + return { + l1, + l2, + } +} + +async function _getProxyAdmin( + contractAddress: string, + provider: Provider +): Promise { + return ( + await _getAddressAtStorageSlot( + contractAddress, + provider, + '0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103' + ) + ).toLowerCase() +} + +async function _getOwner( + contractAddress: string, + provider: Provider +): Promise { + return ( + await IOwnable__factory.connect(contractAddress, provider).owner() + ).toLowerCase() +} + +async function _getAddressAtStorageSlot( + contractAddress: string, + provider: Provider, + storageSlotBytes: string +): Promise { + const storageValue = await provider.getStorageAt( + contractAddress, + storageSlotBytes + ) + + if (!storageValue) { + return '' + } + + // remove excess bytes + const formatAddress = + storageValue.substring(0, 2) + storageValue.substring(26) + + // return address as checksum address + return ethers.utils.getAddress(formatAddress) +} + +interface L1 { + inbox: string + rollupOwner: string + router: string + standardGateway: string + customGateway: string + wethGateway: string + proxyAdmin: string + upgradeExecutor: string +} + +interface L2 { + router: string + standardGateway: string + customGateway: string + wethGateway: string + weth: string + upgradeExecutor: string +} diff --git a/yarn.lock b/yarn.lock index b1b21f7d5d..8185659c93 100644 --- a/yarn.lock +++ b/yarn.lock @@ -23,7 +23,7 @@ optionalDependencies: sol2uml "2.2.0" -"@arbitrum/sdk@^3.1.5": +"@arbitrum/sdk@^3.1.3": version "3.1.9" resolved "https://registry.yarnpkg.com/@arbitrum/sdk/-/sdk-3.1.9.tgz#a511bc70cdd0a947445e4e27833c242ccb77ddf5" integrity sha512-7Rf75cKmQ8nutTV+2JAOXcI6DKG4D7QCJz1JL2nq6hUpRuw4jyKn+irLqJtcIExssylLWt3t/pKV6fYseCYKNg== @@ -738,11 +738,24 @@ resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-waffle/-/hardhat-waffle-2.0.6.tgz#d11cb063a5f61a77806053e54009c40ddee49a54" integrity sha512-+Wz0hwmJGSI17B+BhU/qFRZ1l6/xMW82QGXE/Gi+WTmwgJrQefuBs1lIf7hzQ1hLk6hpkvb/zwcNkpVKRYTQYg== +"@offchainlabs/upgrade-executor@^1.0.0-beta.2": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@offchainlabs/upgrade-executor/-/upgrade-executor-1.0.0.tgz#226e92d116bce4b08f65d4a213bd1e00be08f089" + integrity sha512-leTFFNKS1z19gExXUhg/GZOBmsm2Tj9Ev7viOmFtc2ktgcczox0J2bx3CLQ+4/SgvCkeUH4ZtKoNosW8rgSJaw== + dependencies: + "@openzeppelin/contracts" "4.7.3" + "@openzeppelin/contracts-upgradeable" "4.7.3" + "@openzeppelin/contracts-upgradeable@4.5.2": version "4.5.2" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.5.2.tgz#90d9e47bacfd8693bfad0ac8a394645575528d05" integrity sha512-xgWZYaPlrEOQo3cBj97Ufiuv79SPd8Brh4GcFYhPgb6WvAq4ppz8dWKL6h+jLAK01rUqMRp/TS9AdXgAeNvCLA== +"@openzeppelin/contracts-upgradeable@4.7.3": + version "4.7.3" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.7.3.tgz#f1d606e2827d409053f3e908ba4eb8adb1dd6995" + integrity sha512-+wuegAMaLcZnLCJIvrVUDzA9z/Wp93f0Dla/4jJvIhijRrPabjQbZe6fWiECLaJyfn5ci9fqf9vTw3xpQOad2A== + "@openzeppelin/contracts-upgradeable@4.8.3": version "4.8.3" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.8.3.tgz#6b076a7b751811b90fe3a172a7faeaa603e13a3f" @@ -753,6 +766,11 @@ resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.5.0.tgz#3fd75d57de172b3743cdfc1206883f56430409cc" integrity sha512-fdkzKPYMjrRiPK6K4y64e6GzULR7R7RwxSigHS8DDp7aWDeoReqsQI+cxHV1UuhAqX69L1lAaWDxenfP+xiqzA== +"@openzeppelin/contracts@4.7.3": + version "4.7.3" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.7.3.tgz#939534757a81f8d69cc854c7692805684ff3111e" + integrity sha512-dGRS0agJzu8ybo44pCIf3xBaPQN/65AIXNgK8+4gzKd5kbvlqyxryUYVLJv7fK98Seyd2hDZzVEHSWAh0Bt1Yw== + "@openzeppelin/contracts@4.8.3": version "4.8.3" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.8.3.tgz#cbef3146bfc570849405f59cba18235da95a252a"