diff --git a/packages/contracts-bedrock/scripts/oasys/L1/build/Build.s.sol b/packages/contracts-bedrock/scripts/oasys/L1/build/Build.s.sol index 0161a50a9..9ffeb5af0 100644 --- a/packages/contracts-bedrock/scripts/oasys/L1/build/Build.s.sol +++ b/packages/contracts-bedrock/scripts/oasys/L1/build/Build.s.sol @@ -80,6 +80,8 @@ contract Build is Script { address l2OutputOracleProposer; // L2OutputOracleChallenger is the address of the account that challenges L2 outputs. address l2OutputOracleChallenger; + // MessageRelayer is the address of message-relayer finalizer. + address messageRelayer; // FinalizationPeriodSeconds represents the number of seconds before an output is considered // finalized. This impacts the amount of time that withdrawals take to finalize and is // generally set to 1 week. @@ -219,15 +221,11 @@ contract Build is Script { address l2ooChallenger = vm.envAddress("L2OO_CHALLENGER"); address l2ooProposer = vm.envAddress("L2OO_PROPOSER"); address batchSender = vm.envAddress("BATCH_SENDER"); + address messageRelayer = vm.envAddress("MESSAGE_RELAYER"); uint256 l2ChainId = vm.envUint("L2_CHAIN_ID"); uint256 l1BlockTime = vm.envUint("L1_BLOCK_TIME"); uint256 l2BlockTime = vm.envUint("L2_BLOCK_TIME"); uint256 l2GasLimit = vm.envUint("L2_GAS_LIMIT"); - uint256 finalizationPeriodSeconds = vm.envUint("FINALIZATION_PERIOD_SECONDS"); - uint256 outputOracleSubmissionInterval = vm.envUint("OUTPUT_ORACLE_SUBMISSION_INTERVAL"); - uint256 outputOracleStartingBlockNumber = vm.envUint("OUTPUT_ORACLE_STARTING_BLOCK_NUMBER"); - uint256 outputOracleStartingTimestamp = vm.envUint("OUTPUT_ORACLE_STARTING_TIMESTAMP"); - uint256 l2ZeroFeeTime = vm.envOr("ENABLE_L2_ZERO_FEE", false) ? block.timestamp : 0; // construct a deployment configuration. deployCfg = DeployConfig({ @@ -248,14 +246,16 @@ contract Build is Script { p2pSequencerAddress: p2pSequencer, batchSenderAddress: batchSender, // ---- - l2OutputOracleSubmissionInterval: outputOracleSubmissionInterval, - l2OutputOracleStartingBlockNumber: outputOracleStartingBlockNumber, - l2OutputOracleStartingTimestamp: outputOracleStartingTimestamp, + l2OutputOracleSubmissionInterval: vm.envUint("OUTPUT_ORACLE_SUBMISSION_INTERVAL"), + l2OutputOracleStartingBlockNumber: vm.envUint("OUTPUT_ORACLE_STARTING_BLOCK_NUMBER"), + l2OutputOracleStartingTimestamp: vm.envUint("OUTPUT_ORACLE_STARTING_TIMESTAMP"), // ---- l2OutputOracleProposer: l2ooProposer, l2OutputOracleChallenger: l2ooChallenger, // ---- - finalizationPeriodSeconds: finalizationPeriodSeconds, + messageRelayer: messageRelayer, + // ---- + finalizationPeriodSeconds: vm.envUint("FINALIZATION_PERIOD_SECONDS"), // ---- proxyAdminOwner: finalSystemOwner, baseFeeVaultRecipient: finalSystemOwner, @@ -290,7 +290,7 @@ contract Build is Script { requiredProtocolVersion: bytes32(0), recommendedProtocolVersion: bytes32(0), // ---- - l2ZeroFeeTime: l2ZeroFeeTime, + l2ZeroFeeTime: vm.envOr("ENABLE_L2_ZERO_FEE", false) ? block.timestamp : 0, // set later. batchInboxAddress: address(0), l1StandardBridgeProxy: address(0), @@ -307,6 +307,7 @@ contract Build is Script { l2OutputOracleChallenger: deployCfg.l2OutputOracleChallenger, batchSenderAddress: deployCfg.batchSenderAddress, p2pSequencerAddress: deployCfg.p2pSequencerAddress, + messageRelayer: deployCfg.messageRelayer, l2BlockTime: deployCfg.l2BlockTime, l2GasLimit: uint64(deployCfg.l2GenesisBlockGasLimit), l2OutputOracleSubmissionInterval: deployCfg.l2OutputOracleSubmissionInterval, @@ -334,24 +335,22 @@ contract Build is Script { deposit.deposit{ value: 1 ether }(msg.sender); // build L2. - (address proxyAdmin, address[7] memory proxys,, address batchInbox, address addressManager) = - agent.build(deployCfg.l2ChainID, buildCfg); + (IL1BuildAgent.BuiltAddressList memory results,) = agent.build(deployCfg.l2ChainID, buildCfg); vm.stopBroadcast(); // set deployed addresses - deployCfg.optimismPortalProxy = proxys[0]; - deployCfg.systemConfigProxy = proxys[2]; - deployCfg.l1CrossDomainMessengerProxy = proxys[3]; - deployCfg.l1StandardBridgeProxy = proxys[4]; - deployCfg.l1ERC721BridgeProxy = proxys[5]; - deployCfg.batchInboxAddress = batchInbox; - address protocolVersions = proxys[6]; - address l2OutputOracleProxy = proxys[1]; + deployCfg.optimismPortalProxy = results.oasysPortal; + deployCfg.systemConfigProxy = results.systemConfig; + deployCfg.l1CrossDomainMessengerProxy = results.l1CrossDomainMessenger; + deployCfg.l1StandardBridgeProxy = results.l1StandardBridge; + deployCfg.l1ERC721BridgeProxy = results.l1ERC721Bridge; + deployCfg.batchInboxAddress = results.batchInbox; // output opstack configuration files. string memory deployCfgJson = _deployConfigJson("DeployConfig"); - string memory addressesJson = - _addressesJson("deployed", proxyAdmin, l2OutputOracleProxy, addressManager, protocolVersions); + string memory addressesJson = _addressesJson( + "deployed", results.proxyAdmin, results.oasysL2OutputOracle, address(0), results.protocolVersions + ); // output to the `./tmp/L1BuildAgent/Build/latest` directory _writeJson(deployCfgJson, Path.buildLatestOutDir(), "/deploy-config.json"); @@ -424,6 +423,8 @@ contract Build is Script { json.serialize("l2OutputOracleProposer", deployCfg.l2OutputOracleProposer); json.serialize("l2OutputOracleChallenger", deployCfg.l2OutputOracleChallenger); + json.serialize("messageRelayer", deployCfg.messageRelayer); + json.serialize("finalizationPeriodSeconds", deployCfg.finalizationPeriodSeconds); json.serialize("proxyAdminOwner", deployCfg.proxyAdminOwner); diff --git a/packages/contracts-bedrock/src/L1/L1ERC721Bridge.sol b/packages/contracts-bedrock/src/L1/L1ERC721Bridge.sol index 8cdcc17b8..3a13c1934 100644 --- a/packages/contracts-bedrock/src/L1/L1ERC721Bridge.sol +++ b/packages/contracts-bedrock/src/L1/L1ERC721Bridge.sol @@ -13,7 +13,17 @@ import { Constants } from "src/libraries/Constants.sol"; /// @notice The L1 ERC721 bridge is a contract which works together with the L2 ERC721 bridge to /// make it possible to transfer ERC721 tokens from Ethereum to Optimism. This contract /// acts as an escrow for ERC721 tokens deposited into L2. +/// ------------------------------ +/// Oasys made a change to this contract +// The change is comment out in the mapping of deposits contract L1ERC721Bridge is ERC721Bridge, ISemver { + /// @notice Mapping of L1 token to L2 token to ID to boolean, indicating if the given L1 token + /// by ID was deposited for a given L2 token. + /// ------------------------- + // Move this mapping to the `L1ERC721BridgeLegacySpacer` contract + // To follow the storage layout of Oasys Legacy L1ERC721Bridge + // mapping(address => mapping(address => mapping(uint256 => bool))) public deposits; + /// @notice Semantic version. /// @custom:semver 1.5.0 string public constant version = "1.5.0"; diff --git a/packages/contracts-bedrock/src/L2/L2ERC721Bridge.sol b/packages/contracts-bedrock/src/L2/L2ERC721Bridge.sol index b30dba280..0ae09ade2 100644 --- a/packages/contracts-bedrock/src/L2/L2ERC721Bridge.sol +++ b/packages/contracts-bedrock/src/L2/L2ERC721Bridge.sol @@ -46,7 +46,7 @@ contract L2ERC721Bridge is ERC721Bridge, ISemver { uint256 _tokenId, bytes calldata _extraData ) - external + public virtual onlyOtherBridge { diff --git a/packages/contracts-bedrock/src/oasys/L1/build/BuildOasysPortal.sol b/packages/contracts-bedrock/src/oasys/L1/build/BuildOasysPortal.sol index d4de13fe5..8e539bf4c 100644 --- a/packages/contracts-bedrock/src/oasys/L1/build/BuildOasysPortal.sol +++ b/packages/contracts-bedrock/src/oasys/L1/build/BuildOasysPortal.sol @@ -34,7 +34,7 @@ contract BuildOasysPortal is IBuildOasysPortal, ISemver { } /// @inheritdoc IBuildOasysPortal - function initializeData(bool _paused) external pure returns (bytes memory) { - return abi.encodeCall(OasysPortal.initialize, (_paused)); + function initializeData(bool _paused, address relayer) external pure returns (bytes memory) { + return abi.encodeCall(OasysPortal.initializeWithRelayer, (_paused, relayer)); } } diff --git a/packages/contracts-bedrock/src/oasys/L1/build/BuildProxy.sol b/packages/contracts-bedrock/src/oasys/L1/build/BuildProxy.sol index 4c31b3455..2de53e385 100644 --- a/packages/contracts-bedrock/src/oasys/L1/build/BuildProxy.sol +++ b/packages/contracts-bedrock/src/oasys/L1/build/BuildProxy.sol @@ -5,9 +5,6 @@ import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; import { ISemver } from "src/universal/ISemver.sol"; import { IBuildProxy } from "src/oasys/L1/build/interfaces/IBuildProxy.sol"; import { Proxy } from "src/universal/Proxy.sol"; -import { L1ChugSplashProxy } from "src/legacy/L1ChugSplashProxy.sol"; -import { ResolvedDelegateProxy } from "src/legacy/ResolvedDelegateProxy.sol"; -import { AddressManager } from "src/legacy/AddressManager.sol"; /// @notice Hold the deployment bytecode /// Separate from build contract to avoid bytecode size limitations @@ -16,12 +13,6 @@ contract BuildProxy is IBuildProxy, ISemver { /// @custom:semver 1.0.0 string public constant version = "1.0.0"; - /// @inheritdoc IBuildProxy - function deployAddressManager(address owner) external returns (AddressManager addressManager) { - addressManager = new AddressManager(); - addressManager.transferOwnership(owner); - } - /// @inheritdoc IBuildProxy function deployProxyAdmin(address owner) external returns (ProxyAdmin proxyAdmin) { proxyAdmin = new ProxyAdmin({ _owner: owner }); @@ -31,23 +22,4 @@ contract BuildProxy is IBuildProxy, ISemver { function deployERC1967Proxy(address admin) external returns (Proxy proxy) { proxy = new Proxy({ _admin: admin }); } - - /// @inheritdoc IBuildProxy - function deployChugProxy(address owner) external returns (L1ChugSplashProxy proxy) { - proxy = new L1ChugSplashProxy({ _owner: owner }); - } - - /// @inheritdoc IBuildProxy - function deployResolvedProxy( - address addressManager, - string memory implementationName - ) - external - returns (ResolvedDelegateProxy proxy) - { - proxy = new ResolvedDelegateProxy({ - _addressManager: AddressManager(addressManager), - _implementationName: implementationName - }); - } } diff --git a/packages/contracts-bedrock/src/oasys/L1/build/L1BuildAgent.sol b/packages/contracts-bedrock/src/oasys/L1/build/L1BuildAgent.sol index c88eec465..8d1520e5f 100644 --- a/packages/contracts-bedrock/src/oasys/L1/build/L1BuildAgent.sol +++ b/packages/contracts-bedrock/src/oasys/L1/build/L1BuildAgent.sol @@ -23,6 +23,8 @@ import { IBuildOasysL1ERC721Bridge } from "src/oasys/L1/build/interfaces/IBuildO import { IBuildProtocolVersions } from "src/oasys/L1/build/interfaces/IBuildProtocolVersions.sol"; import { ILegacyL1BuildAgent } from "src/oasys/L1/build/interfaces/ILegacyL1BuildAgent.sol"; import { IOasysL2OutputOracleVerifier } from "src/oasys/L1/interfaces/IOasysL2OutputOracleVerifier.sol"; +import { PortalSender } from "src/oasys/L1/build/PortalSender.sol"; +import { OptimismPortal } from "src/L1/OptimismPortal.sol"; /// @notice The 2nd version of L1BuildAgent /// Regarding the build step, referred to the build script of Opstack @@ -60,7 +62,7 @@ contract L1BuildAgent is IL1BuildAgent, ISemver { string public constant version = "2.0.0"; /// @notice The map of chainId => builder - mapping(uint256 => address) public builders; + mapping(uint256 => address) private builders; /// @notice The map of chainId => BuiltAddressList mapping(uint256 => BuiltAddressList) public builtLists; @@ -71,6 +73,9 @@ contract L1BuildAgent is IL1BuildAgent, ISemver { /// https://betterprogramming.pub/issues-of-returning-arrays-of-dynamic-size-in-solidity-smart-contracts-dd1e54424235 uint256[] public chainIds; + /// @notice The flag to pause the L1CrossDomainMessenger + mapping(uint256 => bool) public messengerPauseds; + constructor( IBuildProxy _bProxy, IBuildOasysL2OutputOracle _bOasysL2OO, @@ -98,6 +103,51 @@ contract L1BuildAgent is IL1BuildAgent, ISemver { L2OO_VERIFIER = _l2ooVerifier; } + /// @notice Pause the legacy L1CrossDomainMessenger + /// This is used when upgrading the existing L2 + /// Ref: step2 of `SystemDictator` + /// https://github.com/oasysgames/oasys-opstack/blob/cd7c58349542f9f1ce9fd42c9054aeed1325e02c/packages/contracts-bedrock/contracts/deployment/SystemDictator.sol + /// @param addressManager The address manager of the existing L2 + function pauseLegacyL1CrossDomainMessenger(uint256 _chainId, address addressManager) public { + require(getBuilderGlobally(_chainId) == msg.sender, "L1BuildAgent: inconsistent builder"); + require(!messengerPauseds[_chainId], "L1BuildAgent: already paused"); + + messengerPauseds[_chainId] = true; + + // Temporarily brick the L1CrossDomainMessenger by setting its implementation address to + // address(0) which will cause the ResolvedDelegateProxy to revert. Better than pausing + // the L1CrossDomainMessenger via pause() because it can be easily reverted. + AddressManager(addressManager).setAddress("OVM_L1CrossDomainMessenger", address(0)); + + // Set the DTL shutoff block, which will tell the DTL to stop syncing new deposits from the + // CanonicalTransactionChain. We do this by setting an address in the AddressManager + // because the DTL already has a reference to the AddressManager and this way we don't also + // need to give it a reference to the SystemDictator. + AddressManager(addressManager).setAddress("DTL_SHUTOFF_BLOCK", address(uint160(block.number))); + } + + /// @notice Unpause the legacy L1CrossDomainMessenger + /// @param addressManager The address manager of the existing L2 + /// @param oldL1CrossDomainMessenger The address of the old L1CrossDomainMessenger + function unpauseLegacyL1CrossDomainMessenger( + uint256 _chainId, + address addressManager, + address oldL1CrossDomainMessenger + ) + public + { + require(getBuilderGlobally(_chainId) == msg.sender, "L1BuildAgent: inconsistent builder"); + require(messengerPauseds[_chainId], "L1BuildAgent: not paused"); + + messengerPauseds[_chainId] = false; + + // Reset the L1CrossDomainMessenger to the old implementation. + AddressManager(addressManager).setAddress("OVM_L1CrossDomainMessenger", oldL1CrossDomainMessenger); + + // Unset the DTL shutoff block which will allow the DTL to sync again. + AddressManager(addressManager).setAddress("DTL_SHUTOFF_BLOCK", address(0)); + } + /// @notice Deploy the L1 contract set to build Verse, This is th main function. /// @param _chainId The chainId of Verse /// @param _cfg The configuration of the L1 contract set @@ -106,77 +156,111 @@ contract L1BuildAgent is IL1BuildAgent, ISemver { BuildConfig calldata _cfg ) external - returns (address, address[7] memory, address[7] memory, address, address) + returns (BuiltAddressList memory, address[7] memory) { + // Only the builder can build the L2 + // The builder is the person who deposits the required amount + address builder = msg.sender; + // Not require to be globally unique, as the pre built L2 needs to be upgraded require(_isInternallyUniqueChainId(_chainId), "L1BuildAgent: already deployed"); if (_requiresDepositCheck(_chainId)) { require( - L1_BUILD_DEPOSIT.getDepositTotal(msg.sender) >= L1_BUILD_DEPOSIT.requiredAmount(), + L1_BUILD_DEPOSIT.getDepositTotal(builder) >= L1_BUILD_DEPOSIT.requiredAmount(), "deposit amount shortage" ); } // build the deposit. // Mark this builder as built. - L1_BUILD_DEPOSIT.build(msg.sender); + L1_BUILD_DEPOSIT.build(builder); // register the builder // Mark this chainId as built - builders[_chainId] = msg.sender; + builders[_chainId] = builder; + + // check if the L2 is upgrading the existing L2 + (bool isUpgrading, address addressManager) = isUpgradingExistingL2(_chainId); // temporarily set the admin to this contract // transfer ownership to the final system owner at the end of building address admin = address(this); - // deploy the AddressManager. - // TODO: Not required for new L2. - address addressManager = address(BUILD_PROXY.deployAddressManager({ owner: admin })); - // deploy proxy contracts for each verse - (ProxyAdmin proxyAdmin, address[7] memory proxys) = _deployProxies(admin, addressManager); - - // transfer ownership of the address manager to the ProxyAdmin - _transferAddressManagerOwnership(proxyAdmin, addressManager); + ProxyAdmin proxyAdmin = _deployProxies(_chainId, admin, addressManager); + + if (isUpgrading) { + // Verify the builder is consistent with chain id; + require(getBuilderGlobally(_chainId) == builder, "L1BuildAgent: inconsistent builder"); + + if (!messengerPauseds[_chainId]) { + // Pause the legacy L1CrossDomainMessenger + pauseLegacyL1CrossDomainMessenger(_chainId, addressManager); + } + // Remove deprecated addresses from the AddressManager + _removeDeprecatedAddresses(addressManager); + // Set the address of the AddressManager. + proxyAdmin.setAddressManager(AddressManager(addressManager)); + require(proxyAdmin.addressManager() == AddressManager(addressManager)); + // transfer ownership of the address manager to the ProxyAdmin + AddressManager(addressManager).transferOwnership(address(proxyAdmin)); + } // don't deploy the implementation contracts every time // to save gas, reuse the same implementation contract for each proxy - address[7] memory impls = _deployImplementations(_cfg, proxys); - - // compute the batch inbox address from chainId - // L2 tx bathch is sent to this address - address batchInbox = computeInboxAddress(_chainId); + address[7] memory impls = _deployImplementations(_chainId, _cfg); - emit Deployed(_chainId, _cfg.finalSystemOwner, address(proxyAdmin), proxys, impls, batchInbox, addressManager); - - // register built addresses to the builtLists - builtLists[_chainId].proxyAdmin = address(proxyAdmin); - builtLists[_chainId].systemConfig = proxys[2]; - builtLists[_chainId].l1StandardBridge = proxys[4]; - builtLists[_chainId].l1ERC721Bridge = proxys[5]; - builtLists[_chainId].l1CrossDomainMessenger = proxys[3]; - builtLists[_chainId].oasysL2OutputOracle = proxys[1]; - builtLists[_chainId].oasysPortal = proxys[0]; - builtLists[_chainId].protocolVersions = proxys[6]; - builtLists[_chainId].batchInbox = batchInbox; - builtLists[_chainId].addressManager = addressManager; + emit Deployed(_chainId, _cfg.finalSystemOwner, builtLists[_chainId], impls); // append the chainId to the list chainIds.push(_chainId); // initialize each contracts by calling `initialize` functions through proxys - _initializeSystemConfig(_cfg, proxyAdmin, impls[2], proxys); - _initializeL1StandardBridge(proxyAdmin, impls[4], proxys); - _initializeL1ERC721Bridge(proxyAdmin, impls[5], proxys); - _initializeL1CrossDomainMessenger(proxyAdmin, impls[3], proxys); - _initializeOasysL2OutputOracle(_cfg, proxyAdmin, impls[1], proxys); - _initializeOasysPortal(proxyAdmin, impls[0], proxys); - _initializeProtocolVersions(_cfg, proxyAdmin, impls[6], proxys); + _initializeSystemConfig(_chainId, _cfg, proxyAdmin, impls[2]); + // OasysPortal should be initialized before L1StandardBridge, + // because L1StandardBridge uses OasysPortal as a recipient of the ETH + _initializeOasysPortal(_chainId, _cfg, proxyAdmin, impls[0]); + _initializeL1StandardBridge(_chainId, proxyAdmin, impls[4], isUpgrading); + _initializeL1ERC721Bridge(_chainId, proxyAdmin, impls[5], isUpgrading); + _initializeL1CrossDomainMessenger(_chainId, proxyAdmin, impls[3], isUpgrading); + _initializeOasysL2OutputOracle(_chainId, _cfg, proxyAdmin, impls[1]); + _initializeProtocolVersions(_chainId, _cfg, proxyAdmin, impls[6]); // transfer ownership of the proxy admin to the final system owner _transferProxyAdminOwnership(_cfg, proxyAdmin); - return (address(proxyAdmin), proxys, impls, batchInbox, addressManager); + return (builtLists[_chainId], impls); + } + + /// @notice Return builder address corresponding to the chainId. traverse the legacy build agent + /// @param _chainId The chainId of Verse + function getBuilderGlobally(uint256 _chainId) public view returns (address builder) { + if (LEGACY_L1_BUILD_AGENT != ILegacyL1BuildAgent(address(0))) { + uint256 howMany = 255; // type(uint256).max; -> this leads memory allocation error + (address[] memory _builders, uint256[] memory _chainIds,) = LEGACY_L1_BUILD_AGENT.getBuilts(0, howMany); + for (uint256 i = 0; i < _chainIds.length; i++) { + if (_chainIds[i] == _chainId) { + return _builders[i]; + } + } + } + return getBuilderInternally(_chainId); + } + + /// @notice Return builder address corresponding to the chainId within this contract + /// @param _chainId The chainId of Verse + function getBuilderInternally(uint256 _chainId) public view returns (address) { + return builders[_chainId]; + } + + /// @notice Check if the L2 is upgrading the existing L2 + /// @param _chainId The chainId of Verse + function isUpgradingExistingL2(uint256 _chainId) public view returns (bool, address) { + if (LEGACY_L1_BUILD_AGENT == ILegacyL1BuildAgent(address(0))) { + return (false, address(0)); + } + address addressManager = LEGACY_L1_BUILD_AGENT.getAddressManager(_chainId); + return (addressManager != address(0), addressManager); } /// @notice Compute inbox address from chainId @@ -217,25 +301,29 @@ contract L1BuildAgent is IL1BuildAgent, ISemver { } function _deployProxies( + uint256 _chainId, address admin, address addressManager ) internal - returns (ProxyAdmin proxyAdmin, address[7] memory proxys) + returns (ProxyAdmin proxyAdmin) { proxyAdmin = BUILD_PROXY.deployProxyAdmin({ owner: admin }); - proxys[0] = _deployProxy(address(proxyAdmin)); // OasysPortalProxy - proxys[1] = _deployProxy(address(proxyAdmin)); // OasysL2OutputOracleProxy - proxys[2] = _deployProxy(address(proxyAdmin)); // SystemConfigProxy - proxys[3] = _deployL1CrossDomainMessengerProxy(addressManager); // L1CrossDomainMessengerProxy - proxys[4] = _deployL1StandardBridgeProxy(address(proxyAdmin)); // L1StandardBridgeProxy - proxys[5] = _deployProxy(address(proxyAdmin)); // L1ERC721BridgeProxy - proxys[6] = _deployProxy(address(proxyAdmin)); // ProtocolVersionsProxy - - // Set the address of the AddressManager. - // TODO: Not required for new L2. - proxyAdmin.setAddressManager(AddressManager(addressManager)); - require(proxyAdmin.addressManager() == AddressManager(addressManager)); + + // register built addresses to the builtLists + builtLists[_chainId].proxyAdmin = address(proxyAdmin); + builtLists[_chainId].oasysPortal = _deployProxy(address(proxyAdmin)); + builtLists[_chainId].oasysL2OutputOracle = _deployProxy(address(proxyAdmin)); + builtLists[_chainId].systemConfig = _deployProxy(address(proxyAdmin)); + builtLists[_chainId].l1CrossDomainMessenger = + _deployL1CrossDomainMessengerProxy(address(proxyAdmin), addressManager); + builtLists[_chainId].l1StandardBridge = _deployL1StandardBridgeProxy(address(proxyAdmin), addressManager); + builtLists[_chainId].l1ERC721Bridge = _deployL1ERC721BridgeProxy(address(proxyAdmin), addressManager); + builtLists[_chainId].protocolVersions = _deployProxy(address(proxyAdmin)); + + // compute the batch inbox address from chainId + // L2 tx bathch is sent to this address + builtLists[_chainId].batchInbox = computeInboxAddress(_chainId); } /// @notice Deploy the Proxy @@ -244,43 +332,66 @@ contract L1BuildAgent is IL1BuildAgent, ISemver { } /// @notice Deploy the L1CrossDomainMessengerProxy using a ResolvedDelegateProxy - function _deployL1CrossDomainMessengerProxy(address _addressManager) internal returns (address addr) { - AddressManager addressManager = AddressManager(_addressManager); - - string memory contractName = "OVM_L1CrossDomainMessenger"; - // TODO: Not required for new L2. - ResolvedDelegateProxy proxy = - BUILD_PROXY.deployResolvedProxy({ addressManager: _addressManager, implementationName: contractName }); - - address contractAddr = addressManager.getAddress(contractName); - if (contractAddr != address(proxy)) { - addressManager.setAddress(contractName, address(proxy)); + function _deployL1CrossDomainMessengerProxy( + address proxyAdmin, + address addressManager + ) + internal + returns (address addr) + { + if (addressManager != address(0)) { + // upgrading existing L2 + // Don't deply proxy, as the existing L2 already has the proxy(RelolvedDelegateProxy) + string memory contractName = "Proxy__OVM_L1CrossDomainMessenger"; + addr = AddressManager(addressManager).getAddress(contractName); + require(addr != address(0), "L1BuildAgent: failed to find L1CrossDomainMessengerProxy from AddressManager"); + } else { + addr = _deployProxy(address(proxyAdmin)); } + } - require(addressManager.getAddress(contractName) == address(proxy)); - - addr = address(proxy); + function _deployL1StandardBridgeProxy(address proxyAdmin, address addressManager) internal returns (address addr) { + if (addressManager != address(0)) { + // upgrading existing L2 + // Don't deply proxy, as the existing L2 already has the proxy(RelolvedDelegateProxy) + string memory contractName = "Proxy__OVM_L1StandardBridge"; + addr = AddressManager(addressManager).getAddress(contractName); + require(addr != address(0), "L1BuildAgent: failed to find L1StandardBridgeProxy from AddressManager"); + // Trasfer ownership to ProxyAdmin + L1ChugSplashProxy(payable(addr)).setOwner(address(proxyAdmin)); + } else { + addr = _deployProxy(address(proxyAdmin)); + } } - function _deployL1StandardBridgeProxy(address admin) internal returns (address addr) { - // TODO: Not required for new L2. - addr = address(BUILD_PROXY.deployChugProxy({ owner: admin })); + function _deployL1ERC721BridgeProxy(address proxyAdmin, address addressManager) internal returns (address addr) { + if (addressManager != address(0)) { + // upgrading existing L2 + // Don't deply proxy, as the existing L2 already has the proxy(RelolvedDelegateProxy) + string memory contractName = "Proxy__OVM_L1ERC721Bridge"; + addr = AddressManager(addressManager).getAddress(contractName); + require(addr != address(0), "L1BuildAgent: failed to find L1ERC721BridgeProxy from AddressManager"); + // Trasfer ownership to ProxyAdmin + L1ChugSplashProxy(payable(addr)).setOwner(address(proxyAdmin)); + } else { + addr = _deployProxy(address(proxyAdmin)); + } } /// @notice Deploy all of the implementations function _deployImplementations( - BuildConfig calldata _cfg, - address[7] memory proxys + uint256 _chainId, + BuildConfig calldata _cfg ) internal returns (address[7] memory impls) { impls[0] = _deployImplementation( BUILD_OASYS_PORTAL.deployBytecode({ - _l2Oracle: proxys[1], // OasysL2OutputOracleProxy + _l2Oracle: builtLists[_chainId].oasysL2OutputOracle, _guardian: _cfg.finalSystemOwner, - _systemConfig: proxys[2] // SystemConfigProxy - }) + _systemConfig: builtLists[_chainId].systemConfig + }) ); address _challenger = _cfg.l2OutputOracleChallenger; @@ -302,19 +413,19 @@ contract L1BuildAgent is IL1BuildAgent, ISemver { impls[3] = _deployImplementation( BUILD_L1CROSS_DOMAIN_MESSENGER.deployBytecode({ - _portal: payable(proxys[0]) // OasysPortalProxy + _portal: payable(builtLists[_chainId].oasysPortal) // OasysPortalProxy }) ); impls[4] = _deployImplementation( BUILD_L1_STANDARD_BRIDGE.deployBytecode({ - _messenger: payable(proxys[3]) // L1CrossDomainMessengerProxy + _messenger: payable(builtLists[_chainId].l1CrossDomainMessenger) // L1CrossDomainMessengerProxy }) ); impls[5] = _deployImplementation( BUILD_OASYS_L1_ERC721_BRIDGE.deployBytecode({ - _messenger: proxys[3], // L1CrossDomainMessengerProxy + _messenger: builtLists[_chainId].l1CrossDomainMessenger, // L1CrossDomainMessengerProxy _otherBridge: L2PredeployAddresses.L2_ERC721_BRIDGE }) ); @@ -332,14 +443,14 @@ contract L1BuildAgent is IL1BuildAgent, ISemver { /// @notice Initialize the SystemConfig function _initializeSystemConfig( + uint256 _chainId, BuildConfig calldata _cfg, ProxyAdmin proxyAdmin, - address impl, - address[7] memory proxys + address impl ) internal { - address systemConfigProxy = proxys[2]; + address systemConfigProxy = builtLists[_chainId].systemConfig; proxyAdmin.upgradeAndCall({ _proxy: payable(systemConfigProxy), @@ -366,54 +477,78 @@ contract L1BuildAgent is IL1BuildAgent, ISemver { } /// @notice Initialize the L1StandardBridge - function _initializeL1StandardBridge(ProxyAdmin proxyAdmin, address impl, address[7] memory proxys) internal { - address l1StandardBridgeProxy = proxys[4]; + function _initializeL1StandardBridge( + uint256 _chainId, + ProxyAdmin proxyAdmin, + address impl, + bool isUpgrading + ) + internal + { + address l1StandardBridgeProxy = builtLists[_chainId].l1StandardBridge; - uint256 proxyType = uint256(proxyAdmin.proxyType(l1StandardBridgeProxy)); - if (proxyType != uint256(ProxyAdmin.ProxyType.CHUGSPLASH)) { + if (isUpgrading) { + // The proxy of Legacy L2 is L1ChugSplashProxy, so need to set type proxyAdmin.setProxyType(l1StandardBridgeProxy, ProxyAdmin.ProxyType.CHUGSPLASH); + require(uint256(proxyAdmin.proxyType(l1StandardBridgeProxy)) == uint256(ProxyAdmin.ProxyType.CHUGSPLASH)); + + // Transfer ETH from the L1StandardBridge to the OptimismPortal. + PortalSender portalSender = new PortalSender(OptimismPortal(payable(builtLists[_chainId].oasysPortal))); + proxyAdmin.upgradeAndCall( + payable(l1StandardBridgeProxy), address(portalSender), abi.encodeCall(PortalSender.donate, ()) + ); } - require(uint256(proxyAdmin.proxyType(l1StandardBridgeProxy)) == uint256(ProxyAdmin.ProxyType.CHUGSPLASH)); - // TODO: Built L2 uses L1ChugSplashProxy and requires special handling. proxyAdmin.upgrade({ _proxy: payable(l1StandardBridgeProxy), _implementation: impl }); } /// @notice Initialize the L1ERC721Bridge - function _initializeL1ERC721Bridge(ProxyAdmin proxyAdmin, address impl, address[7] memory proxys) internal { - address l1ERC721BridgeProxy = proxys[5]; + function _initializeL1ERC721Bridge( + uint256 _chainId, + ProxyAdmin proxyAdmin, + address impl, + bool isUpgrading + ) + internal + { + address l1ERC721BridgeProxy = builtLists[_chainId].l1ERC721Bridge; + + if (isUpgrading) { + // The proxy of Legacy L2 is L1ChugSplashProxy, so need to set type + proxyAdmin.setProxyType(l1ERC721BridgeProxy, ProxyAdmin.ProxyType.CHUGSPLASH); + require(uint256(proxyAdmin.proxyType(l1ERC721BridgeProxy)) == uint256(ProxyAdmin.ProxyType.CHUGSPLASH)); + } - // TODO: Built L2 uses L1ChugSplashProxy and requires special handling. proxyAdmin.upgrade({ _proxy: payable(l1ERC721BridgeProxy), _implementation: impl }); } /// @notice Initialize the L1CrossDomainMessenger function _initializeL1CrossDomainMessenger( + uint256 _chainId, ProxyAdmin proxyAdmin, address impl, - address[7] memory proxys + bool isUpgrading ) internal { - address l1CrossDomainMessengerProxy = proxys[3]; + address l1CrossDomainMessengerProxy = builtLists[_chainId].l1CrossDomainMessenger; - uint256 proxyType = uint256(proxyAdmin.proxyType(l1CrossDomainMessengerProxy)); - if (proxyType != uint256(ProxyAdmin.ProxyType.RESOLVED)) { + if (isUpgrading) { + // The proxy of Legacy L2 is ResolvedDelegateProxy, so need to set type and implementation name + // Set proxy type to RESOLVED proxyAdmin.setProxyType(l1CrossDomainMessengerProxy, ProxyAdmin.ProxyType.RESOLVED); - } - require(uint256(proxyAdmin.proxyType(l1CrossDomainMessengerProxy)) == uint256(ProxyAdmin.ProxyType.RESOLVED)); - - string memory contractName = "OVM_L1CrossDomainMessenger"; - string memory implName = proxyAdmin.implementationName(impl); - if (keccak256(bytes(contractName)) != keccak256(bytes(implName))) { + require( + uint256(proxyAdmin.proxyType(l1CrossDomainMessengerProxy)) == uint256(ProxyAdmin.ProxyType.RESOLVED) + ); + // Set the implementation name to OVM_L1CrossDomainMessenger + string memory contractName = "OVM_L1CrossDomainMessenger"; proxyAdmin.setImplementationName(l1CrossDomainMessengerProxy, contractName); + require( + keccak256(bytes(proxyAdmin.implementationName(l1CrossDomainMessengerProxy))) + == keccak256(bytes(contractName)) + ); } - require( - keccak256(bytes(proxyAdmin.implementationName(l1CrossDomainMessengerProxy))) - == keccak256(bytes(contractName)) - ); - // TODO: Built L2 uses OVM_L1CrossDomainMessengerProxy and requires special handling. proxyAdmin.upgradeAndCall({ _proxy: payable(l1CrossDomainMessengerProxy), _implementation: impl, @@ -423,14 +558,14 @@ contract L1BuildAgent is IL1BuildAgent, ISemver { /// @notice Initialize the OasysL2OutputOracle function _initializeOasysL2OutputOracle( + uint256 _chainId, BuildConfig calldata _cfg, ProxyAdmin proxyAdmin, - address impl, - address[7] memory proxys + address impl ) internal { - address l2OutputOracleProxy = proxys[1]; + address l2OutputOracleProxy = builtLists[_chainId].oasysL2OutputOracle; proxyAdmin.upgradeAndCall({ _proxy: payable(l2OutputOracleProxy), @@ -443,25 +578,32 @@ contract L1BuildAgent is IL1BuildAgent, ISemver { } /// @notice Initialize the OasysPortal - function _initializeOasysPortal(ProxyAdmin proxyAdmin, address impl, address[7] memory proxys) internal { - address oasysPortalProxy = proxys[0]; + function _initializeOasysPortal( + uint256 _chainId, + BuildConfig calldata _cfg, + ProxyAdmin proxyAdmin, + address impl + ) + internal + { + address oasysPortalProxy = builtLists[_chainId].oasysPortal; proxyAdmin.upgradeAndCall({ _proxy: payable(oasysPortalProxy), _implementation: impl, - _data: BUILD_OASYS_PORTAL.initializeData({ _paused: false }) + _data: BUILD_OASYS_PORTAL.initializeData({ _paused: false, relayer: _cfg.messageRelayer }) }); } function _initializeProtocolVersions( + uint256 _chainId, BuildConfig calldata _cfg, ProxyAdmin proxyAdmin, - address impl, - address[7] memory proxys + address impl ) internal { - address protocolVersionsProxy = proxys[6]; + address protocolVersionsProxy = builtLists[_chainId].protocolVersions; uint256 requiredProtocolVersion = uint256(0x0); uint256 recommendedProtocolVersion = uint256(0x0); @@ -477,6 +619,34 @@ contract L1BuildAgent is IL1BuildAgent, ISemver { }); } + // Ref: step3 of `SystemDictator` + // https://github.com/oasysgames/oasys-opstack/blob/cd7c58349542f9f1ce9fd42c9054aeed1325e02c/packages/contracts-bedrock/contracts/deployment/SystemDictator.sol + function _removeDeprecatedAddresses(address addressManager) internal { + // Remove all deprecated addresses from the AddressManager + string[17] memory deprecated = [ + "OVM_CanonicalTransactionChain", + "OVM_L2CrossDomainMessenger", + "OVM_DecompressionPrecompileAddress", + "OVM_Sequencer", + "OVM_Proposer", + "OVM_ChainStorageContainer-CTC-batches", + "OVM_ChainStorageContainer-CTC-queue", + "OVM_CanonicalTransactionChain", + "OVM_StateCommitmentChain", + "OVM_BondManager", + "OVM_ExecutionManager", + "OVM_FraudVerifier", + "OVM_StateManagerFactory", + "OVM_StateTransitionerFactory", + "OVM_SafetyChecker", + "OVM_L1MultiMessageRelayer", + "BondManager" + ]; + for (uint256 i = 0; i < deprecated.length; i++) { + AddressManager(addressManager).setAddress(deprecated[i], address(0)); + } + } + /// @notice Transfer ownership of the ProxyAdmin contract to the final system owner function _transferProxyAdminOwnership(BuildConfig calldata _cfg, ProxyAdmin proxyAdmin) internal { address owner = proxyAdmin.owner(); @@ -485,12 +655,4 @@ contract L1BuildAgent is IL1BuildAgent, ISemver { proxyAdmin.transferOwnership(finalSystemOwner); } } - - /// @notice Transfer ownership of the address manager to the ProxyAdmin - function _transferAddressManagerOwnership(ProxyAdmin proxyAdmin, address _addressManager) internal { - AddressManager addressManager = AddressManager(_addressManager); - if (addressManager.owner() != address(proxyAdmin)) { - addressManager.transferOwnership(address(proxyAdmin)); - } - } } diff --git a/packages/contracts-bedrock/src/oasys/L1/build/L1BuildDeposit.sol b/packages/contracts-bedrock/src/oasys/L1/build/L1BuildDeposit.sol index c763bfc6b..d3c5fe79f 100644 --- a/packages/contracts-bedrock/src/oasys/L1/build/L1BuildDeposit.sol +++ b/packages/contracts-bedrock/src/oasys/L1/build/L1BuildDeposit.sol @@ -34,4 +34,24 @@ contract L1BuildDeposit is ISemver, LegacyL1BuildDeposit { function getDepositTotalIncludeLegacy(address _builder) public view returns (uint256) { return getDepositTotal(_builder) + legacyL1BuildDeposit.getDepositTotal(_builder); } + + /** + * Returns the whether the address is the builder globally. + * @param _builder Address of the Verse-Builder. + */ + function isBuilderGlobally(address _builder) public view returns (bool) { + bool builtLegacy; + if (legacyL1BuildDeposit != IL1BuildDeposit(address(0))) { + builtLegacy = legacyL1BuildDeposit.getBuildBlock(_builder) > 0; + } + return builtLegacy || isBuilderInternally(_builder); + } + + /** + * Returns the whether the address is the internal builder. + * @param _builder Address of the Verse-Builder. + */ + function isBuilderInternally(address _builder) public view returns (bool) { + return getBuildBlock(_builder) > 0; + } } diff --git a/packages/contracts-bedrock/src/oasys/L1/build/PortalSender.sol b/packages/contracts-bedrock/src/oasys/L1/build/PortalSender.sol new file mode 100644 index 000000000..110c34b86 --- /dev/null +++ b/packages/contracts-bedrock/src/oasys/L1/build/PortalSender.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { OptimismPortal } from "src/L1/OptimismPortal.sol"; + +/** + * @title PortalSender + * @notice The PortalSender is a simple intermediate contract that will transfer the balance of the + * L1StandardBridge to the OptimismPortal during the Bedrock migration. + */ +// Copied from +// https://github.com/oasysgames/oasys-opstack/blob/cd7c58349542f9f1ce9fd42c9054aeed1325e02c/packages/contracts-bedrock/contracts/deployment/PortalSender.sol +contract PortalSender { + /** + * @notice Address of the OptimismPortal contract. + */ + OptimismPortal public immutable PORTAL; + + /** + * @param _portal Address of the OptimismPortal contract. + */ + constructor(OptimismPortal _portal) { + PORTAL = _portal; + } + + /** + * @notice Sends balance of this contract to the OptimismPortal. + */ + function donate() public { + PORTAL.donateETH{ value: address(this).balance }(); + } +} diff --git a/packages/contracts-bedrock/src/oasys/L1/build/interfaces/IBuildOasysPortal.sol b/packages/contracts-bedrock/src/oasys/L1/build/interfaces/IBuildOasysPortal.sol index 6262a9f39..bf571fce8 100644 --- a/packages/contracts-bedrock/src/oasys/L1/build/interfaces/IBuildOasysPortal.sol +++ b/packages/contracts-bedrock/src/oasys/L1/build/interfaces/IBuildOasysPortal.sol @@ -17,5 +17,6 @@ interface IBuildOasysPortal { /// @notice Return data for initializer. /// @param _paused Sets the contract's pausability state. - function initializeData(bool _paused) external pure returns (bytes memory); + /// @param relayer Sets the messager relayer + function initializeData(bool _paused, address relayer) external pure returns (bytes memory); } diff --git a/packages/contracts-bedrock/src/oasys/L1/build/interfaces/IBuildProxy.sol b/packages/contracts-bedrock/src/oasys/L1/build/interfaces/IBuildProxy.sol index 1bde78df1..b36f8c290 100644 --- a/packages/contracts-bedrock/src/oasys/L1/build/interfaces/IBuildProxy.sol +++ b/packages/contracts-bedrock/src/oasys/L1/build/interfaces/IBuildProxy.sol @@ -1,17 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { AddressManager } from "src/legacy/AddressManager.sol"; import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; import { Proxy } from "src/universal/Proxy.sol"; -import { L1ChugSplashProxy } from "src/legacy/L1ChugSplashProxy.sol"; -import { ResolvedDelegateProxy } from "src/legacy/ResolvedDelegateProxy.sol"; interface IBuildProxy { - /// @notice Deploy the AddressManager. - /// @param owner Initial owner of the contract. - function deployAddressManager(address owner) external returns (AddressManager addressManager); - /// @notice Deploy the proxyAdmin. /// @param owner Initial owner of the contract. function deployProxyAdmin(address owner) external returns (ProxyAdmin proxyAdmin); @@ -19,18 +12,4 @@ interface IBuildProxy { /// @notice Deploy the Proxy. /// @param admin Initial admin of the contract. function deployERC1967Proxy(address admin) external returns (Proxy proxy); - - /// @notice Deploy the L1ChugSplashProxy. - /// @param owner Initial owner of the contract. - function deployChugProxy(address owner) external returns (L1ChugSplashProxy proxy); - - /// @notice Deploy the ResolvedDelegateProxy. - /// @param addressManager Address of the AddressManager. - /// @param implementationName implementationName of the contract to proxy to. - function deployResolvedProxy( - address addressManager, - string memory implementationName - ) - external - returns (ResolvedDelegateProxy proxy); } diff --git a/packages/contracts-bedrock/src/oasys/L1/build/interfaces/IL1BuildAgent.sol b/packages/contracts-bedrock/src/oasys/L1/build/interfaces/IL1BuildAgent.sol index 746aa0b0d..2138e287d 100644 --- a/packages/contracts-bedrock/src/oasys/L1/build/interfaces/IL1BuildAgent.sol +++ b/packages/contracts-bedrock/src/oasys/L1/build/interfaces/IL1BuildAgent.sol @@ -19,6 +19,9 @@ interface IL1BuildAgent { // This address sign the block for p2p sync. // Value: depending on each verse address p2pSequencerAddress; + /// The address of messager relayer. + // Value: depending on each verse + address messageRelayer; // the block time of l2 chain // Value: 2s uint256 l2BlockTime; @@ -58,41 +61,30 @@ interface IL1BuildAgent { address oasysPortal; address protocolVersions; address batchInbox; - address addressManager; } /// @notice Event emitted when the L1 contract set is deployed - event Deployed( - uint256 indexed chainId, - address owner, - address proxyAdmin, - address[7] proxys, - address[7] impls, - address batchInbox, - address addressManager - ); + event Deployed(uint256 indexed chainId, address finalSystemOwner, BuiltAddressList results, address[7] impls); function builtLists(uint256 chainId) external view - returns (address, address, address, address, address, address, address, address, address, address); + returns (address, address, address, address, address, address, address, address, address); function chainIds(uint256 index) external view returns (uint256 chainId); + function getBuilderGlobally(uint256 chainId) external view returns (address builder); + function computeInboxAddress(uint256 chainId) external view returns (address batchInbox); function isUniqueChainId(uint256 chainId) external view returns (bool); + function isUpgradingExistingL2(uint256 _chainId) external returns (bool, address); + function build( uint256 chainId, BuildConfig calldata cfg ) external - returns ( - address proxyAdmin, - address[7] memory proxys, - address[7] memory impls, - address batchInbox, - address addressManager - ); + returns (BuiltAddressList memory, address[7] memory); } diff --git a/packages/contracts-bedrock/src/oasys/L1/build/interfaces/IL1BuildDeposit.sol b/packages/contracts-bedrock/src/oasys/L1/build/interfaces/IL1BuildDeposit.sol index cc14f1644..2b391e8a7 100644 --- a/packages/contracts-bedrock/src/oasys/L1/build/interfaces/IL1BuildDeposit.sol +++ b/packages/contracts-bedrock/src/oasys/L1/build/interfaces/IL1BuildDeposit.sol @@ -11,4 +11,8 @@ interface IL1BuildDeposit { function deposit(address _builder) external payable; function withdraw(address _builder, uint256 _amount) external; + + function getBuildBlock(address _builder) external view returns (uint256); + + function isBuilderGlobally(address _builder) external view returns (bool); } diff --git a/packages/contracts-bedrock/src/oasys/L1/build/interfaces/ILegacyL1BuildAgent.sol b/packages/contracts-bedrock/src/oasys/L1/build/interfaces/ILegacyL1BuildAgent.sol index 422a173d2..69646104a 100644 --- a/packages/contracts-bedrock/src/oasys/L1/build/interfaces/ILegacyL1BuildAgent.sol +++ b/packages/contracts-bedrock/src/oasys/L1/build/interfaces/ILegacyL1BuildAgent.sol @@ -3,4 +3,11 @@ pragma solidity 0.8.15; interface ILegacyL1BuildAgent { function getAddressManager(uint256 _chainId) external view returns (address); + function getBuilts( + uint256 cursor, + uint256 howMany + ) + external + view + returns (address[] memory, uint256[] memory, uint256); } diff --git a/packages/contracts-bedrock/src/oasys/L1/build/legacy/LegacyL1BuildDeposit.sol b/packages/contracts-bedrock/src/oasys/L1/build/legacy/LegacyL1BuildDeposit.sol index 39f036e1e..8c8cc177f 100644 --- a/packages/contracts-bedrock/src/oasys/L1/build/legacy/LegacyL1BuildDeposit.sol +++ b/packages/contracts-bedrock/src/oasys/L1/build/legacy/LegacyL1BuildDeposit.sol @@ -198,7 +198,7 @@ contract LegacyL1BuildDeposit { * @param _builder Address of the Verse-Builder. * @return block Block number. */ - function getBuildBlock(address _builder) external view returns (uint256) { + function getBuildBlock(address _builder) public view returns (uint256) { return _buildBlock[_builder]; } diff --git a/packages/contracts-bedrock/src/oasys/L1/messaging/OasysERC721BridgeLegacySpacer.sol b/packages/contracts-bedrock/src/oasys/L1/messaging/OasysERC721BridgeLegacySpacer.sol new file mode 100644 index 000000000..ee98eb4a7 --- /dev/null +++ b/packages/contracts-bedrock/src/oasys/L1/messaging/OasysERC721BridgeLegacySpacer.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { L2OutputOracle } from "src/L1/L2OutputOracle.sol"; + +/// @title OasysERC721BridgeLegacySpacer +/// @notice Defines the storage layout of Oasys Legacy ERC721Bridge. +/// Ref: +/// https://github.com/oasysgames/oasys-optimism/blob/4d667a169296f37422ffaa4901e8d149e84abe5a/packages/contracts/contracts/oasys/L1/messaging/L1ERC721BridgeV2.sol +/// Ref: +/// https://github.com/oasysgames/oasys-optimism/blob/4d667a169296f37422ffaa4901e8d149e84abe5a/packages/contracts/contracts/oasys/L2/messaging/L2ERC721Bridge.sol +contract OasysERC721BridgeLegacySpacer { + /// @custom:legacy + /// @custom:spacer messenger + /// @notice Spacer for backwards compatibility. + address private spacer_0_0_20; + + /// @custom:legacy + /// @custom:spacer l1ERC721Bridge,l2ERC721Bridge + /// @notice Spacer for backwards compatibility. + address private spacer_1_0_20; + + /// @notice Maps the deposit status of L1 token to L2 token + /// This mapping is commonly used in Oasy's L1ERC721Bridge and Optimism's L1ERC721Bridge + mapping(address => mapping(address => mapping(uint256 => bool))) public deposits; + + /// @notice Reserve extra slots (to a total of 50) in the storage layout for future upgrades. + /// A gap size of 47 was chosen here, so that the first slot used in a child contract + /// would be a multiple of 50. + uint256[47] private __gap; +} diff --git a/packages/contracts-bedrock/src/oasys/L1/messaging/OasysL1ERC721Bridge.sol b/packages/contracts-bedrock/src/oasys/L1/messaging/OasysL1ERC721Bridge.sol index 8ba65583f..cb4e3afbf 100644 --- a/packages/contracts-bedrock/src/oasys/L1/messaging/OasysL1ERC721Bridge.sol +++ b/packages/contracts-bedrock/src/oasys/L1/messaging/OasysL1ERC721Bridge.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; +import { Address } from "@openzeppelin/contracts/utils/Address.sol"; import { L1ERC721Bridge } from "src/L1/L1ERC721Bridge.sol"; import { ILegacyL1ERC721Bridge } from "src/oasys/L1/interfaces/ILegacyL1ERC721Bridge.sol"; @@ -29,8 +30,18 @@ contract OasysL1ERC721Bridge is L1ERC721Bridge, ILegacyL1ERC721Bridge { bytes calldata _data ) external - onlyEOA { + // Copied from ERC721Bridge.bridgeERC721 + // start ---------------------------- + // Modifier requiring sender to be EOA. This prevents against a user error that would occur + // if the sender is a smart contract wallet that has a different address on the remote chain + // (or doesn't have an address on the remote chain at all). The user would fail to receive + // the NFT if they use this function because it sends the NFT to the same address as the + // caller. This check could be bypassed by a malicious contract via initcode, but it takes + // care of the user error we want to avoid. + require(!Address.isContract(msg.sender), "ERC721Bridge: account is not externally owned"); + // ------------------------------ end + _initiateBridgeERC721(_l1Token, _l2Token, msg.sender, msg.sender, _tokenId, _l2Gas, _data); } @@ -46,7 +57,10 @@ contract OasysL1ERC721Bridge is L1ERC721Bridge, ILegacyL1ERC721Bridge { ) external { - require(_to != address(0), "L1ERC721Bridge: nft recipient cannot be address(0)"); + // Copied from ERC721Bridge.bridgeERC721To + // start ---------------------------- + require(_to != address(0), "ERC721Bridge: nft recipient cannot be address(0)"); + // ------------------------------ end _initiateBridgeERC721(_l1Token, _l2Token, msg.sender, _to, _tokenId, _l2Gas, _data); } @@ -67,6 +81,7 @@ contract OasysL1ERC721Bridge is L1ERC721Bridge, ILegacyL1ERC721Bridge { } /// @inheritdoc L1ERC721Bridge + /// @dev Emits an legacy ERC721WithdrawalFinalized event for backwards compatibility. function finalizeBridgeERC721( address _localToken, address _remoteToken, @@ -80,11 +95,14 @@ contract OasysL1ERC721Bridge is L1ERC721Bridge, ILegacyL1ERC721Bridge { { super.finalizeBridgeERC721(_localToken, _remoteToken, _from, _to, _tokenId, _extraData); + // Ref: + // https://github.com/oasysgames/oasys-optimism/blob/4d667a169296f37422ffaa4901e8d149e84abe5a/packages/contracts/contracts/oasys/L1/messaging/IL1ERC721Bridge.sol#L21-L28 // slither-disable-next-line reentrancy-events emit ERC721WithdrawalFinalized(_localToken, _remoteToken, _from, _to, _tokenId, _extraData); } /// @inheritdoc L1ERC721Bridge + /// @dev Emits an legacy ERC721DepositInitiated event for backwards compatibility. function _initiateBridgeERC721( address _localToken, address _remoteToken, @@ -99,6 +117,8 @@ contract OasysL1ERC721Bridge is L1ERC721Bridge, ILegacyL1ERC721Bridge { { super._initiateBridgeERC721(_localToken, _remoteToken, _from, _to, _tokenId, _minGasLimit, _extraData); + // Ref: + // https://github.com/oasysgames/oasys-optimism/blob/4d667a169296f37422ffaa4901e8d149e84abe5a/packages/contracts/contracts/oasys/L1/messaging/IL1ERC721Bridge.sol#L12-L19 // slither-disable-next-line reentrancy-events emit ERC721DepositInitiated(_localToken, _remoteToken, _from, _to, _tokenId, _extraData); } diff --git a/packages/contracts-bedrock/src/oasys/L1/messaging/OasysPortal.sol b/packages/contracts-bedrock/src/oasys/L1/messaging/OasysPortal.sol index 7c43581af..114d23397 100644 --- a/packages/contracts-bedrock/src/oasys/L1/messaging/OasysPortal.sol +++ b/packages/contracts-bedrock/src/oasys/L1/messaging/OasysPortal.sol @@ -36,6 +36,12 @@ contract OasysPortal is OptimismPortal { super.initialize(_paused); } + /// @notice Initalize with setting messager relayer + function initializeWithRelayer(bool _paused, address _messageRelayer) public { + messageRelayer = _messageRelayer; + initialize(_paused); + } + /// @notice Set a new message relayer address. /// If the zero address is set, no immediate relay of withdrawal messages. function setMessageRelayer(address newRelayer) external { diff --git a/packages/contracts-bedrock/src/oasys/L1/rollup/OasysL2OutputOracle.sol b/packages/contracts-bedrock/src/oasys/L1/rollup/OasysL2OutputOracle.sol index 7cdc1aad8..e52f34389 100644 --- a/packages/contracts-bedrock/src/oasys/L1/rollup/OasysL2OutputOracle.sol +++ b/packages/contracts-bedrock/src/oasys/L1/rollup/OasysL2OutputOracle.sol @@ -51,6 +51,23 @@ contract OasysL2OutputOracle is IOasysL2OutputOracle, L2OutputOracle { super.initialize(_startingBlockNumber, _startingTimestamp); } + /// @notice Update the starting block number and timestamp. + /// Anyone can call, until the first output is recorded or deleted all the outputs. + /// This function for the purpose of attempting the L2 upgrade again, + /// after the L2 Upgrade fails and rollback operations are conducted. + /// @param _startingBlockNumber Block number for the first recoded L2 block. + /// @param _startingTimestamp Timestamp for the first recoded L2 block. + function updateStartingBlock(uint256 _startingBlockNumber, uint256 _startingTimestamp) public { + require( + _startingTimestamp <= block.timestamp, + "L2OutputOracle: starting L2 timestamp must be less than current time" + ); + require(l2Outputs.length == 0, "L2OutputOracle: cannot update starting block after outputs have been recorded"); + + startingTimestamp = _startingTimestamp; + startingBlockNumber = _startingBlockNumber; + } + /// @notice Getter function for the address of the OasysL2OutputOracleVerifier on this chain. /// @notice Address of the OasysL2OutputOracleVerifier on this chain. function l2OracleVerifier() public view returns (IOasysL2OutputOracleVerifier) { diff --git a/packages/contracts-bedrock/src/oasys/L2/messaging/OasysL2ERC721Bridge.sol b/packages/contracts-bedrock/src/oasys/L2/messaging/OasysL2ERC721Bridge.sol index 3ac6c9171..091de3742 100644 --- a/packages/contracts-bedrock/src/oasys/L2/messaging/OasysL2ERC721Bridge.sol +++ b/packages/contracts-bedrock/src/oasys/L2/messaging/OasysL2ERC721Bridge.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.15; import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import { Address } from "@openzeppelin/contracts/utils/Address.sol"; import { L1ERC721Bridge } from "src/L1/L1ERC721Bridge.sol"; import { L2ERC721Bridge } from "src/L2/L2ERC721Bridge.sol"; import { ILegacyL2ERC721Bridge } from "src/oasys/L2/interfaces/ILegacyL2ERC721Bridge.sol"; @@ -21,18 +22,35 @@ contract OasysL2ERC721Bridge is L2ERC721Bridge, ILegacyL2ERC721Bridge { /// @custom:legacy /// @inheritdoc ILegacyL2ERC721Bridge + /// @dev Ref: + /// https://github.com/oasysgames/oasys-optimism/blob/4d667a169296f37422ffaa4901e8d149e84abe5a/packages/contracts/contracts/oasys/L2/messaging/L2ERC721Bridge.sol#L30 function l1ERC721Bridge() external view returns (address) { return OTHER_BRIDGE; } /// @custom:legacy /// @inheritdoc ILegacyL2ERC721Bridge - function withdraw(address _l2Token, uint256 _tokenId, uint32 _l1Gas, bytes calldata _data) external onlyEOA { - _initiateBridgeERC721(_l2Token, _getRemoteToken(_l2Token), msg.sender, msg.sender, _tokenId, _l1Gas, _data); + /// @dev Ref: + /// https://github.com/oasysgames/oasys-optimism/blob/4d667a169296f37422ffaa4901e8d149e84abe5a/packages/contracts/contracts/oasys/L2/messaging/L2ERC721Bridge.sol#L53-L58 + function withdraw(address _l2Token, uint256 _tokenId, uint32 _l1Gas, bytes calldata _data) external { + // Copied from ERC721Bridge.bridgeERC721 + // start ---------------------------- + // Modifier requiring sender to be EOA. This prevents against a user error that would occur + // if the sender is a smart contract wallet that has a different address on the remote chain + // (or doesn't have an address on the remote chain at all). The user would fail to receive + // the NFT if they use this function because it sends the NFT to the same address as the + // caller. This check could be bypassed by a malicious contract via initcode, but it takes + // care of the user error we want to avoid. + require(!Address.isContract(msg.sender), "ERC721Bridge: account is not externally owned"); + // ------------------------------ end + + _initiateBridgeERC721(_l2Token, _l2Token, msg.sender, msg.sender, _tokenId, _l1Gas, _data); } /// @custom:legacy /// @inheritdoc ILegacyL2ERC721Bridge + /// @dev Ref: + /// https://github.com/oasysgames/oasys-optimism/blob/4d667a169296f37422ffaa4901e8d149e84abe5a/packages/contracts/contracts/oasys/L2/messaging/L2ERC721Bridge.sol#L65-L71 function withdrawTo( address _l2Token, address _to, @@ -42,13 +60,18 @@ contract OasysL2ERC721Bridge is L2ERC721Bridge, ILegacyL2ERC721Bridge { ) external { - require(_to != address(0), "L2ERC721Bridge: nft recipient cannot be address(0)"); + // Copied from ERC721Bridge.bridgeERC721To + // start ---------------------------- + require(_to != address(0), "ERC721Bridge: nft recipient cannot be address(0)"); + // ------------------------------ end - _initiateBridgeERC721(_l2Token, _getRemoteToken(_l2Token), msg.sender, _to, _tokenId, _l1Gas, _data); + _initiateBridgeERC721(_l2Token, _l2Token, msg.sender, _to, _tokenId, _l1Gas, _data); } /// @custom:legacy /// @inheritdoc ILegacyL2ERC721Bridge + /// @dev Ref: + /// https://github.com/oasysgames/oasys-optimism/blob/4d667a169296f37422ffaa4901e8d149e84abe5a/packages/contracts/contracts/oasys/L2/messaging/L2ERC721Bridge.sol#L130-L137 function finalizeDeposit( address _l1Token, address _l2Token, @@ -57,13 +80,13 @@ contract OasysL2ERC721Bridge is L2ERC721Bridge, ILegacyL2ERC721Bridge { uint256 _tokenId, bytes calldata _data ) - external - onlyOtherBridge + public { - _finalizeBridgeERC721(_l1Token, _l2Token, _from, _to, _tokenId, _data); + finalizeBridgeERC721(_l1Token, _l2Token, _from, _to, _tokenId, _data); } /// @inheritdoc L2ERC721Bridge + /// @dev override to support legacy L2ERC721Bridge function finalizeBridgeERC721( address _localToken, address _remoteToken, @@ -72,11 +95,43 @@ contract OasysL2ERC721Bridge is L2ERC721Bridge, ILegacyL2ERC721Bridge { uint256 _tokenId, bytes calldata _extraData ) - external + public override onlyOtherBridge { - _finalizeBridgeERC721(_localToken, _remoteToken, _from, _to, _tokenId, _extraData); + if (_isOptimismMintableToken(_localToken)) { + // Proceed with the original implementation if the local token is optimism mintable + super.finalizeBridgeERC721(_localToken, _remoteToken, _from, _to, _tokenId, _extraData); + } else { + // Following implementation is for legacy L2StandardERC721 + // Mostly copied from the original implementation + require(_localToken != address(this), "L2ERC721Bridge: local token cannot be self"); + + // Note that supportsInterface makes a callback to the _localToken address which is user + // provided. + require( + ERC165Checker.supportsInterface(_localToken, type(ILegacyL2StandardERC721).interfaceId), + "L2ERC721Bridge: local token interface is not compliant" + ); + + require( + // Legacy token references the remote token as `l1Token` + _remoteToken == ILegacyL2StandardERC721(_localToken).l1Token(), + "L2ERC721Bridge: wrong remote token for Oasys Legacy ERC721 local token" + ); + + // When a deposit is finalized, we give the NFT with the same tokenId to the account + // on L2. Note that safeMint makes a callback to the _to address which is user provided. + // Legacy token does not have safeMint, so we use mint instead + ILegacyL2StandardERC721(_localToken).mint(_to, _tokenId); + + // slither-disable-next-line reentrancy-events + emit ERC721BridgeFinalized(_localToken, _remoteToken, _from, _to, _tokenId, _extraData); + } + + // Emit Legacy event for backward compatibility + // slither-disable-next-line reentrancy-events + emit DepositFinalized(_remoteToken, _localToken, _from, _to, _tokenId, _extraData); } /// @inheritdoc L2ERC721Bridge @@ -92,65 +147,55 @@ contract OasysL2ERC721Bridge is L2ERC721Bridge, ILegacyL2ERC721Bridge { internal override { - require(_remoteToken != address(0), "L2ERC721Bridge: remote token cannot be address(0)"); - require(_remoteToken == _getRemoteToken(_localToken), "L2ERC721Bridge: remote token does not match given value"); - - // Check that the withdrawal is being initiated by the NFT owner - require( - _from == IERC721(_localToken).ownerOf(_tokenId), - "L2ERC721Bridge: Withdrawal is not being initiated by NFT owner" - ); - - // Construct calldata for l1ERC721Bridge.finalizeBridgeERC721(_to, _tokenId) - // When a withdrawal is initiated, we burn the withdrawer's NFT to prevent subsequent L2 usage - // slither-disable-next-line reentrancy-events - _burnLocalToken(_localToken, _from, _tokenId); + // Keep remote token for the legacy + address remoteToken; - bytes memory message = abi.encodeWithSelector( - L1ERC721Bridge.finalizeBridgeERC721.selector, _remoteToken, _localToken, _from, _to, _tokenId, _extraData - ); + if (_isOptimismMintableToken(_localToken)) { + remoteToken = _remoteToken; - // Send message to L1 bridge - // slither-disable-next-line reentrancy-events - MESSENGER.sendMessage(OTHER_BRIDGE, message, _minGasLimit); + // Proceed with the original implementation if the local token is optimism mintable + super._initiateBridgeERC721(_localToken, remoteToken, _from, _to, _tokenId, _minGasLimit, _extraData); + } else { + // Following implementation is for legacy L2StandardERC721 + // Mostly copied from the original implementation + + require(_remoteToken != address(0), "L2ERC721Bridge: remote token cannot be address(0)"); + + // Check that the withdrawal is being initiated by the NFT owner + require( + _from == IERC721(_localToken).ownerOf(_tokenId), + "L2ERC721Bridge: Withdrawal is not being initiated by NFT owner" + ); + + // Construct calldata for l1ERC721Bridge.finalizeBridgeERC721(_to, _tokenId) + // Legacy token references the remote token as `l1Token` + // slither-disable-next-line reentrancy-events + remoteToken = ILegacyL2StandardERC721(_localToken).l1Token(); + // Skip the following check because the legacy interfaces (withdraw, withdrawTo) do not specify the correct + // remote token, + // resulting in failures. + // require(remoteToken == _remoteToken, "L2ERC721Bridge: remote token does not match given value"); + + // When a withdrawal is initiated, we burn the withdrawer's NFT to prevent subsequent L2 + // usage + // slither-disable-next-line reentrancy-events + ILegacyL2StandardERC721(_localToken).burn(_from, _tokenId); - // slither-disable-next-line reentrancy-events - emit ERC721BridgeInitiated(_localToken, _remoteToken, _from, _to, _tokenId, _extraData); - // slither-disable-next-line reentrancy-events - emit WithdrawalInitiated(_remoteToken, _localToken, _from, _to, _tokenId, _extraData); - } + bytes memory message = abi.encodeWithSelector( + L1ERC721Bridge.finalizeBridgeERC721.selector, remoteToken, _localToken, _from, _to, _tokenId, _extraData + ); - /// @notice Completes an ERC721 bridge from the other domain and sends the ERC721 token to the - /// recipient on this domain. - /// @param _localToken Address of the ERC721 token on this domain. - /// @param _remoteToken Address of the ERC721 token on the other domain. - /// @param _from Address that triggered the bridge on the other domain. - /// @param _to Address to receive the token on this domain. - /// @param _tokenId ID of the token being deposited. - /// @param _extraData Optional data to forward to L1. - /// Data supplied here will not be used to execute any code on L1 and is - /// only emitted as extra data for the convenience of off-chain tooling. - function _finalizeBridgeERC721( - address _localToken, - address _remoteToken, - address _from, - address _to, - uint256 _tokenId, - bytes calldata _extraData - ) - internal - { - require(_localToken != address(this), "L2ERC721Bridge: local token cannot be self"); - require(_remoteToken == _getRemoteToken(_localToken), "L2ERC721Bridge: remote token does not match given value"); + // Send message to L1 bridge + // slither-disable-next-line reentrancy-events + MESSENGER.sendMessage(OTHER_BRIDGE, message, _minGasLimit); - // When a deposit is finalized, we give the NFT with the same tokenId to the account - // on L2. Note that safeMint makes a callback to the _to address which is user provided. - _mintLocalToken(_localToken, _to, _tokenId); + // slither-disable-next-line reentrancy-events + emit ERC721BridgeInitiated(_localToken, remoteToken, _from, _to, _tokenId, _extraData); + } + // Emit Legacy event for backward compatibility // slither-disable-next-line reentrancy-events - emit ERC721BridgeFinalized(_localToken, _remoteToken, _from, _to, _tokenId, _extraData); - // slither-disable-next-line reentrancy-events - emit DepositFinalized(_remoteToken, _localToken, _from, _to, _tokenId, _extraData); + emit WithdrawalInitiated(remoteToken, _localToken, _from, _to, _tokenId, _extraData); } /// @notice Determine if the local token is an ILegacyL2StandardERC721. @@ -166,47 +211,4 @@ contract OasysL2ERC721Bridge is L2ERC721Bridge, ILegacyL2ERC721Bridge { function _isOptimismMintableToken(address _localToken) internal view returns (bool) { return ERC165Checker.supportsInterface(_localToken, type(IOptimismMintableERC721).interfaceId); } - - /// @notice Returns the address of the remote token that is paired with the local token. - /// Note: Will revert if the local token is not ILegacyL2StandardERC721 or OptimismMintableERC721. - /// @param _localToken Address of the local token. - /// @return Address of the remote token. - function _getRemoteToken(address _localToken) internal view returns (address) { - if (_isLegacyStandardToken(_localToken)) { - return ILegacyL2StandardERC721(_localToken).l1Token(); - } else if (_isOptimismMintableToken(_localToken)) { - return IOptimismMintableERC721(_localToken).remoteToken(); - } else { - revert("L2ERC721Bridge: local token interface is not compliant"); - } - } - - /// @notice Mints some token ID for a user, checking first that contract recipients - /// are aware of the ERC721 protocol to prevent tokens from being forever locked. - /// @param _localToken Address of the local token. - /// @param _to Address of the user to mint the token for. - /// @param _tokenId Token ID to mint. - function _mintLocalToken(address _localToken, address _to, uint256 _tokenId) internal { - if (_isLegacyStandardToken(_localToken)) { - ILegacyL2StandardERC721(_localToken).mint(_to, _tokenId); - } else if (_isOptimismMintableToken(_localToken)) { - IOptimismMintableERC721(_localToken).safeMint(_to, _tokenId); - } else { - revert("L2ERC721Bridge: local token interface is not compliant"); - } - } - - /// @notice Burns a token ID from a user. - /// @param _localToken Address of the local token. - /// @param _from Address of the user to burn the token from. - /// @param _tokenId Token ID to burn. - function _burnLocalToken(address _localToken, address _from, uint256 _tokenId) internal { - if (_isLegacyStandardToken(_localToken)) { - ILegacyL2StandardERC721(_localToken).burn(_from, _tokenId); - } else if (_isOptimismMintableToken(_localToken)) { - IOptimismMintableERC721(_localToken).burn(_from, _tokenId); - } else { - revert("L2ERC721Bridge: local token interface is not compliant"); - } - } } diff --git a/packages/contracts-bedrock/src/universal/ERC721Bridge.sol b/packages/contracts-bedrock/src/universal/ERC721Bridge.sol index bad5d58dc..00a8666e8 100644 --- a/packages/contracts-bedrock/src/universal/ERC721Bridge.sol +++ b/packages/contracts-bedrock/src/universal/ERC721Bridge.sol @@ -4,10 +4,11 @@ pragma solidity 0.8.15; import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol"; import { Address } from "@openzeppelin/contracts/utils/Address.sol"; import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import { OasysERC721BridgeLegacySpacer } from "src/oasys/L1/messaging/OasysERC721BridgeLegacySpacer.sol"; /// @title ERC721Bridge /// @notice ERC721Bridge is a base contract for the L1 and L2 ERC721 bridges. -abstract contract ERC721Bridge { +abstract contract ERC721Bridge is OasysERC721BridgeLegacySpacer { /// @notice Messenger contract on this domain. This will be removed in the /// future, use `messenger` instead. /// @custom:legacy @@ -18,21 +19,8 @@ abstract contract ERC721Bridge { /// @custom:legacy address public immutable OTHER_BRIDGE; - /// @custom:legacy - /// @custom:spacer messenger - /// @notice Spacer for backwards compatibility. - address private spacer_0_0_20; - - /// @custom:legacy - /// @custom:spacer l1ERC721Bridge / l2ERC721Bridge - /// @notice Spacer for backwards compatibility. - address private spacer_1_0_20; - - /// @notice Mapping that stores deposits for a given pair of local and remote tokens. - mapping(address => mapping(address => mapping(uint256 => bool))) public deposits; - /// @notice Reserve extra slots (to a total of 50) in the storage layout for future upgrades. - uint256[47] private __gap; + uint256[49] private __gap; /// @notice Emitted when an ERC721 bridge to the other network is initiated. /// @param localToken Address of the token on this domain. @@ -66,17 +54,6 @@ abstract contract ERC721Bridge { bytes extraData ); - /// @notice Modifier requiring sender to be EOA. This prevents against a user error that would occur - /// if the sender is a smart contract wallet that has a different address on the remote chain - /// (or doesn't have an address on the remote chain at all). The user would fail to receive - /// the NFT if they use this function because it sends the NFT to the same address as the - /// caller. This check could be bypassed by a malicious contract via initcode, but it takes - /// care of the user error we want to avoid. - modifier onlyEOA() { - require(!Address.isContract(msg.sender), "ERC721Bridge: account is not externally owned"); - _; - } - /// @notice Ensures that the caller is a cross-chain message from the other bridge. modifier onlyOtherBridge() { require( @@ -132,8 +109,15 @@ abstract contract ERC721Bridge { bytes calldata _extraData ) external - onlyEOA { + // Modifier requiring sender to be EOA. This prevents against a user error that would occur + // if the sender is a smart contract wallet that has a different address on the remote chain + // (or doesn't have an address on the remote chain at all). The user would fail to receive + // the NFT if they use this function because it sends the NFT to the same address as the + // caller. This check could be bypassed by a malicious contract via initcode, but it takes + // care of the user error we want to avoid. + require(!Address.isContract(msg.sender), "ERC721Bridge: account is not externally owned"); + _initiateBridgeERC721(_localToken, _remoteToken, msg.sender, msg.sender, _tokenId, _minGasLimit, _extraData); } diff --git a/packages/contracts-bedrock/test/L1BuildAgent.t.sol b/packages/contracts-bedrock/test/L1BuildAgent.t.sol index d3294f4ac..ed10159b8 100644 --- a/packages/contracts-bedrock/test/L1BuildAgent.t.sol +++ b/packages/contracts-bedrock/test/L1BuildAgent.t.sol @@ -2,295 +2,314 @@ pragma solidity 0.8.15; -import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; - import { console2 as console } from "forge-std/console2.sol"; import { stdStorage, StdStorage } from "forge-std/Test.sol"; +import { TestERC20 } from "test/mocks/TestERC20.sol"; +import { TestERC721 } from "test/mocks/TestERC721.sol"; import { Constants } from "src/libraries/Constants.sol"; import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; -import { ResourceMetering } from "src/L1/ResourceMetering.sol"; -import { ProtocolVersion } from "src/L1/ProtocolVersions.sol"; - -import { SetupL1BuildAgent } from "test/setup/oasys/SetupL1BuildAgent.sol"; - -contract TestERC721 is ERC721 { - constructor() ERC721("Test", "TST") { } - - function mint(address to, uint256 tokenId) public { - _mint(to, tokenId); - } -} - -contract L1BuildAgentTest is SetupL1BuildAgent { +import { AddressManager } from "src/legacy/AddressManager.sol"; +import { L1CrossDomainMessenger } from "src/L1/L1CrossDomainMessenger.sol"; +import { IL1BuildAgent } from "src/oasys/L1/build/interfaces/IL1BuildAgent.sol"; +import { ResolvedDelegateProxy } from "src/legacy/ResolvedDelegateProxy.sol"; +import { L1ChugSplashProxy } from "src/legacy/L1ChugSplashProxy.sol"; +import { OptimismPortal } from "src/L1/OptimismPortal.sol"; +import { L1StandardBridge } from "src/L1/L1StandardBridge.sol"; +import { L1ERC721Bridge } from "src/L1/L1ERC721Bridge.sol"; + +import { L1BuildAgentTestCommon } from "test/setup/oasys/SetupL1BuildAgent.sol"; +import { MockLegacyL1BuildAgent } from "test/setup/oasys/MockLegacyL1BuildAgent.sol"; +import { MockLegacyL1StandardBridge } from "test/setup/oasys/MockLegacyL1StandardBridge.sol"; +import { MockLegacyL1ERC721Bridge } from "test/setup/oasys/MockLegacyL1ERC721Bridge.sol"; + +contract L1BuildAgentTest is L1BuildAgentTestCommon { using stdStorage for StdStorage; - function test_batchInbox() external view { - assert(deployment.batchInbox == 0xfF00000000000000000000000000000015B3FF00); + function setUp() public override { + super.setUp(); + + vm.prank(builder); + deployment = _runL1BuildAgent( + 5555, + address(0), + IL1BuildAgent.BuildConfig({ + finalSystemOwner: finalOwner, + l2OutputOracleProposer: proposer, + l2OutputOracleChallenger: challenger, + batchSenderAddress: batcher, + p2pSequencerAddress: p2pSequencer, + messageRelayer: relayer, + l2BlockTime: 5, + l2GasLimit: 50_000_000, + l2OutputOracleSubmissionInterval: 50, + finalizationPeriodSeconds: 5 days, + l2OutputOracleStartingBlockNumber: 500, + l2OutputOracleStartingTimestamp: block.timestamp + }) + ); + + console.log("\nWallets"); + console.log("alice : %s", alice); + console.log("bob : %s", bob); + console.log("depositor : %s", depositor); + console.log("builder : %s", builder); + console.log("finalOwner : %s", finalOwner); + console.log("proposer : %s", proposer); + console.log("challenger : %s", challenger); + console.log("batcher : %s", batcher); + console.log("p2pSequencer : %s", p2pSequencer); + console.log("relayer : %s", relayer); + + console.log("\nDependency contracts"); + console.log("PermissionedContractFactory : %s", address(permissionedFactory)); + console.log("OasysL2OutputOracleVerifier : %s", address(l2OracleVerifier)); + + console.log("\nL1 Build contracts"); + console.log("L1BuildAgent : %s", address(l1Agent)); + console.log("L1BuildDeposit : %s", address(l1Deposit)); + + console.log("\nProxies"); + console.log("OasysPortal : %s", address(deployment.portal)); + console.log("OasysL2OutputOracle : %s", address(deployment.l2Oracle)); + console.log("SystemConfig : %s", address(deployment.systemConfig)); + console.log("L1CrossDomainMessenger : %s", address(deployment.l1Messenger)); + console.log("L1StandardBridge : %s", address(deployment.l1ERC20Bridge)); + console.log("L1ERC721Bridge : %s", address(deployment.l1ERC721Bridge)); + console.log("ProtocolVersions : %s", address(deployment.protocolVersions)); + + console.log("\nImplementations"); + console.log("OasysPortal : %s", address(deployment.portalImpl)); + console.log("OasysL2OutputOracle : %s", address(deployment.l2OracleImpl)); + console.log("SystemConfig : %s", address(deployment.systemConfigImpl)); + console.log("L1CrossDomainMessenger : %s", address(deployment.l1MessengerImpl)); + console.log("L1StandardBridge : %s", address(deployment.l1ERC20BridgeImpl)); + console.log("L1ERC721Bridge : %s", address(deployment.l1ERC721BridgeImpl)); + console.log("ProtocolVersions : %s", address(deployment.protocolVersionsImpl)); } /** * @dev Tests for `ProxyAdmin` */ - function test_ProxyAdmin_owner() external view { - assert(deployment.proxyAdmin.owner() == deployment.buildCfg.finalSystemOwner); - } - - function test_ProxyAdmin_addressManager() external view { - assert(address(deployment.proxyAdmin.addressManager()) == address(deployment.addressManager)); - } - function test_ProxyAdmin_proxyTypes() external view { assert(deployment.proxyAdmin.proxyType(address(deployment.portal)) == ProxyAdmin.ProxyType.ERC1967); assert(deployment.proxyAdmin.proxyType(address(deployment.l2Oracle)) == ProxyAdmin.ProxyType.ERC1967); assert(deployment.proxyAdmin.proxyType(address(deployment.systemConfig)) == ProxyAdmin.ProxyType.ERC1967); - assert(deployment.proxyAdmin.proxyType(address(deployment.l1Messenger)) == ProxyAdmin.ProxyType.RESOLVED); - assert(deployment.proxyAdmin.proxyType(address(deployment.l1ERC20Bridge)) == ProxyAdmin.ProxyType.CHUGSPLASH); + assert(deployment.proxyAdmin.proxyType(address(deployment.l1Messenger)) == ProxyAdmin.ProxyType.ERC1967); + assert(deployment.proxyAdmin.proxyType(address(deployment.l1ERC20Bridge)) == ProxyAdmin.ProxyType.ERC1967); assert(deployment.proxyAdmin.proxyType(address(deployment.l1ERC721Bridge)) == ProxyAdmin.ProxyType.ERC1967); assert(deployment.proxyAdmin.proxyType(address(deployment.protocolVersions)) == ProxyAdmin.ProxyType.ERC1967); } +} - function test_ProxyAdmin_implementationNames() external view { - assert( - keccak256(abi.encode(deployment.proxyAdmin.implementationName(address(deployment.l1Messenger)))) - == keccak256(abi.encode("OVM_L1CrossDomainMessenger")) +contract L1BuildAgentUpgradeTest is L1BuildAgentTestCommon { + TestERC20 l1ERC20; + TestERC20 l2ERC20; + TestERC721 l1ERC721; + TestERC721 l2ERC721; + + uint256 depositedAmount; + uint256 depositedTokenId; + + function setUp() public override { + super.setUp(); + + // Deploy legacy address manager, cross, standard bridge, erc721 bridge + (address addressManager,, address l1StandardBridgeProxy, address l1ERC721BridgeProxy) = _deployLegacies(); + + // Deploy ERC20 and ERC721 tokens + _deployTokens(); + + // Deposit ERC20 and ERC721 tokens to each bridges + _depositTokensToBridge(l1StandardBridgeProxy, l1ERC721BridgeProxy); + + // Transfer ownership of the legacy contracts to the builder + _transferOwnerships(addressManager, l1StandardBridgeProxy, l1ERC721BridgeProxy, address(l1Agent)); + + // Mark the following chainId as built + uint256 chainId = 5555; + legacyAgent.setBuilder(builder, chainId); + legacyAgent.setAddressManager(chainId, addressManager); + legacyDeposit.setBuildBlock(builder, block.number); + + vm.prank(builder); + deployment = _runL1BuildAgent( + chainId, + addressManager, + IL1BuildAgent.BuildConfig({ + finalSystemOwner: finalOwner, + l2OutputOracleProposer: proposer, + l2OutputOracleChallenger: challenger, + batchSenderAddress: batcher, + p2pSequencerAddress: p2pSequencer, + messageRelayer: relayer, + l2BlockTime: 5, + l2GasLimit: 50_000_000, + l2OutputOracleSubmissionInterval: 50, + finalizationPeriodSeconds: 5 days, + l2OutputOracleStartingBlockNumber: 500, + l2OutputOracleStartingTimestamp: block.timestamp + }) ); - } - /** - * @dev Tests for `AddressManager` - */ - function test_AddressManager_owner() external view { - assert(deployment.addressManager.owner() == address(deployment.proxyAdmin)); - } - - function test_AddressManager_getAddress_OVM_L1CrossDomainMessenger() external view { - assert( - deployment.addressManager.getAddress("OVM_L1CrossDomainMessenger") == address(deployment.l1MessengerImpl) + // Clear the cross domain messenger address to prevent early deposits + vm.prank(finalOwner); + string memory contractName = "OVM_L1CrossDomainMessenger"; + deployment.proxyAdmin.setImplementationName(address(0), contractName); + } + + function _deployLegacies() internal returns (address, address, address, address) { + // Deploy address manager + AddressManager addressManager = new AddressManager(); + // Deploy proxies + ResolvedDelegateProxy l1CrossDomainMessengerProxy = + new ResolvedDelegateProxy(addressManager, "OVM_L1CrossDomainMessenger"); + L1ChugSplashProxy l1StandardBridgeProxy = new L1ChugSplashProxy(address(this)); + L1ChugSplashProxy l1ERC721BridgeProxy = new L1ChugSplashProxy(address(this)); + // Deploy implementations + L1CrossDomainMessenger l1CrossDomainMessenger = new L1CrossDomainMessenger(OptimismPortal(payable(0))); + MockLegacyL1StandardBridge legacyStandardBridge = new MockLegacyL1StandardBridge(); + MockLegacyL1ERC721Bridge legacyERC721Bridge = new MockLegacyL1ERC721Bridge(); + // Set addresses to address manager + addressManager.setAddress("Proxy__OVM_L1CrossDomainMessenger", address(l1CrossDomainMessengerProxy)); + addressManager.setAddress("Proxy__OVM_L1StandardBridge", address(l1StandardBridgeProxy)); + addressManager.setAddress("Proxy__OVM_L1ERC721Bridge", address(l1ERC721BridgeProxy)); + addressManager.setAddress("OVM_L1CrossDomainMessenger", address(l1CrossDomainMessenger)); + addressManager.setAddress("OVM_CanonicalTransactionChain", address(l1CrossDomainMessenger)); // dummy, expected + // to be set zero value after build + // Transfer ownership of the address manager to the builder + addressManager.transferOwnership(builder); + // Set implementations to proxies + l1StandardBridgeProxy.setStorage( + Constants.PROXY_IMPLEMENTATION_ADDRESS, bytes32(uint256(uint160(address(legacyStandardBridge)))) + ); + l1StandardBridgeProxy.setOwner(builder); + l1ERC721BridgeProxy.setStorage( + Constants.PROXY_IMPLEMENTATION_ADDRESS, bytes32(uint256(uint160(address(legacyERC721Bridge)))) + ); + l1ERC721BridgeProxy.setOwner(builder); + return ( + address(addressManager), + address(l1CrossDomainMessengerProxy), + address(l1StandardBridgeProxy), + address(l1ERC721BridgeProxy) ); } - /** - * @dev Tests for `OasysPortal` - */ - function test_OasysPortal_initialized() external { - vm.expectRevert("Initializable: contract is already initialized"); - deployment.portal.initialize(false); - } - - function test_OasysPortal_L2_ORACLE() external view { - assert(address(deployment.portal.L2_ORACLE()) == address(deployment.l2Oracle)); - assert(address(deployment.portalImpl.L2_ORACLE()) == address(deployment.l2Oracle)); - } - - function test_OasysPortal_SYSTEM_CONFIG() external view { - assert(address(deployment.portal.SYSTEM_CONFIG()) == address(deployment.systemConfig)); - assert(address(deployment.portalImpl.SYSTEM_CONFIG()) == address(deployment.systemConfig)); - } - - function test_OasysPortal_GUARDIAN() external view { - assert(address(deployment.portal.GUARDIAN()) == deployment.buildCfg.finalSystemOwner); - assert(address(deployment.portalImpl.GUARDIAN()) == deployment.buildCfg.finalSystemOwner); - } - - function test_OasysPortal_l2Sender() external view { - assert(deployment.portal.l2Sender() == 0x000000000000000000000000000000000000dEaD); - assert(deployment.portalImpl.l2Sender() == 0x000000000000000000000000000000000000dEaD); - } - - function test_OasysPortal_paused() external view { - assert(deployment.portal.paused() == false); - assert(deployment.portalImpl.paused() == true); - } - - function test_OasysPortal_messageRelayer() external view { - assert(deployment.portal.messageRelayer() == address(0)); - assert(deployment.portalImpl.messageRelayer() == address(0)); - } - - /** - * @dev Tests for `OasysL2OutputOracle` - */ - function test_OasysL2OutputOracle_initialized() external { - vm.expectRevert("Initializable: contract is already initialized"); - deployment.l2Oracle.initialize(0, 0); - } - - function test_OasysL2OutputOracle_SUBMISSION_INTERVAL() external view { - assert(deployment.l2Oracle.SUBMISSION_INTERVAL() == deployment.buildCfg.l2OutputOracleSubmissionInterval); - assert(deployment.l2OracleImpl.SUBMISSION_INTERVAL() == deployment.buildCfg.l2OutputOracleSubmissionInterval); - } - - function test_OasysL2OutputOracle_L2_BLOCK_TIME() external view { - assert(deployment.l2Oracle.L2_BLOCK_TIME() == deployment.buildCfg.l2BlockTime); - assert(deployment.l2OracleImpl.L2_BLOCK_TIME() == deployment.buildCfg.l2BlockTime); - } - - function test_OasysL2OutputOracle_CHALLENGER() external view { - assert(deployment.l2Oracle.CHALLENGER() == deployment.buildCfg.l2OutputOracleChallenger); - assert(deployment.l2OracleImpl.CHALLENGER() == deployment.buildCfg.l2OutputOracleChallenger); - } - - function testOasysL2OutputOracle__PROPOSER() external view { - assert(deployment.l2Oracle.PROPOSER() == deployment.buildCfg.l2OutputOracleProposer); - assert(deployment.l2OracleImpl.PROPOSER() == deployment.buildCfg.l2OutputOracleProposer); - } - - function test_OasysL2OutputOracle_FINALIZATION_PERIOD_SECONDS() external view { - assert(deployment.l2Oracle.FINALIZATION_PERIOD_SECONDS() == deployment.buildCfg.finalizationPeriodSeconds); - assert(deployment.l2OracleImpl.FINALIZATION_PERIOD_SECONDS() == deployment.buildCfg.finalizationPeriodSeconds); - } - - function test_OasysL2OutputOracle_VERIFIER() external view { - assert(address(deployment.l2Oracle.VERIFIER()) == address(l2OracleVerifier)); - assert(address(deployment.l2OracleImpl.VERIFIER()) == address(l2OracleVerifier)); + function _deployTokens() internal { + l1ERC20 = new TestERC20(); + l2ERC20 = new TestERC20(); + l1ERC721 = new TestERC721(); + l2ERC721 = new TestERC721(); } - function test_OasysL2OutputOracle_startingBlockNumber() external view { - assert(deployment.l2Oracle.startingBlockNumber() == deployment.buildCfg.l2OutputOracleStartingBlockNumber); - assert(deployment.l2OracleImpl.startingBlockNumber() == 0); + function _depositTokensToBridge(address l1StandardBridgeProxy, address l1ERC721BridgeProxy) internal { + // Deposit OAS + depositedAmount = 1 ether; + MockLegacyL1StandardBridge(l1StandardBridgeProxy).depositETH{ value: depositedAmount }(50000, hex""); + // Deposit ERC20 + deal(address(l1ERC20), address(alice), depositedAmount); + vm.prank(alice); + l1ERC20.approve(l1StandardBridgeProxy, depositedAmount); + vm.prank(alice); + MockLegacyL1StandardBridge(l1StandardBridgeProxy).depositERC20( + address(l1ERC20), address(l2ERC20), depositedAmount, 50000, hex"" + ); + // Deposit ERC721 + depositedTokenId = 1243; + l1ERC721.mint(alice, depositedTokenId); + vm.prank(alice); + l1ERC721.approve(l1ERC721BridgeProxy, depositedTokenId); + vm.prank(alice); + MockLegacyL1ERC721Bridge(l1ERC721BridgeProxy).depositERC721( + address(l1ERC721), address(l2ERC721), depositedTokenId, 50000, hex"" + ); } - function test_OasysL2OutputOracle_startingTimestamp() external view { - assert(deployment.l2Oracle.startingTimestamp() == deployment.buildCfg.l2OutputOracleStartingTimestamp); - assert(deployment.l2OracleImpl.startingTimestamp() == 0); + function _transferOwnerships(address manager, address chugProxy1, address chugProxy2, address newOwner) internal { + vm.prank(builder); + AddressManager(manager).transferOwnership(newOwner); + vm.prank(builder); + L1ChugSplashProxy(payable(chugProxy1)).setOwner(newOwner); + vm.prank(builder); + L1ChugSplashProxy(payable(chugProxy2)).setOwner(newOwner); } /** - * @dev Tests for `SystemConfig` + * @dev Tests for `ProxyAdmin` */ - function test_SystemConfig_initialized() external { - ResourceMetering.ResourceConfig memory cfg; - vm.expectRevert("Initializable: contract is already initialized"); - deployment.systemConfig.initialize(address(0), 0, 0, bytes32(0), 0, address(0), cfg); - } - - function test_SystemConfig_owner() external view { - assert(deployment.systemConfig.owner() == deployment.buildCfg.finalSystemOwner); - assert(deployment.systemConfigImpl.owner() == address(0xdEaD)); - } - - function test_SystemConfig_overhead() external view { - assert(deployment.systemConfig.overhead() == 188); - assert(deployment.systemConfigImpl.overhead() == 0); - } - - function test_SystemConfig_scalar() external view { - assert(deployment.systemConfig.scalar() == 684_000); - assert(deployment.systemConfigImpl.scalar() == 0); - } - - function test_SystemConfig_batcherHash() external view { - bytes32 expect = bytes32(uint256(uint160(deployment.buildCfg.batchSenderAddress))); - assert(deployment.systemConfig.batcherHash() == expect); - assert(deployment.systemConfigImpl.batcherHash() == bytes32(0)); - } - - function test_SystemConfig_gasLimit() external view { - assert(deployment.systemConfig.gasLimit() == deployment.buildCfg.l2GasLimit); - assert(deployment.systemConfigImpl.gasLimit() == 20_000_000 + 1_000_000); - } - - function test_SystemConfig_resourceConfig() external view { - ResourceMetering.ResourceConfig memory expect = Constants.DEFAULT_RESOURCE_CONFIG(); - ResourceMetering.ResourceConfig memory actual = deployment.systemConfig.resourceConfig(); - - assert(actual.maxResourceLimit == expect.maxResourceLimit); - assert(actual.elasticityMultiplier == expect.elasticityMultiplier); - assert(actual.baseFeeMaxChangeDenominator == expect.baseFeeMaxChangeDenominator); - assert(actual.minimumBaseFee == expect.minimumBaseFee); - assert(actual.systemTxMaxGas == expect.systemTxMaxGas); - assert(actual.maximumBaseFee == expect.maximumBaseFee); + function test_ProxyAdmin_addressManager() external view { + assert(address(deployment.proxyAdmin.addressManager()) == address(deployment.addressManager)); } - function test_SystemConfig_unsafeBlockSigner() external view { - assert(deployment.systemConfig.unsafeBlockSigner() == deployment.buildCfg.p2pSequencerAddress); - assert(deployment.systemConfigImpl.unsafeBlockSigner() == address(0)); + function test_ProxyAdmin_proxyTypes_succeeds() external view { + assert(deployment.proxyAdmin.proxyType(address(deployment.portal)) == ProxyAdmin.ProxyType.ERC1967); + assert(deployment.proxyAdmin.proxyType(address(deployment.l2Oracle)) == ProxyAdmin.ProxyType.ERC1967); + assert(deployment.proxyAdmin.proxyType(address(deployment.systemConfig)) == ProxyAdmin.ProxyType.ERC1967); + assert(deployment.proxyAdmin.proxyType(address(deployment.l1Messenger)) == ProxyAdmin.ProxyType.RESOLVED); + assert(deployment.proxyAdmin.proxyType(address(deployment.l1ERC20Bridge)) == ProxyAdmin.ProxyType.CHUGSPLASH); + assert(deployment.proxyAdmin.proxyType(address(deployment.l1ERC721Bridge)) == ProxyAdmin.ProxyType.CHUGSPLASH); + assert(deployment.proxyAdmin.proxyType(address(deployment.protocolVersions)) == ProxyAdmin.ProxyType.ERC1967); } - /** - * @dev Tests for `L1CrossDomainMessenger` - */ - function test_L1CrossDomainMessenger_initialized() external { - vm.expectRevert("Initializable: contract is already initialized"); - deployment.l1Messenger.initialize(); + function test_ProxyAdmin_implementationNames() external view { + assert( + // keccak256(abi.encode(deployment.proxyAdmin.implementationName(address(deployment.l1Messenger)))) + keccak256(abi.encode(deployment.proxyAdmin.implementationName(address(0)))) + == keccak256(abi.encode("OVM_L1CrossDomainMessenger")) + ); } - function test_L1CrossDomainMessenger_OTHER_MESSENGER() external view { - assert(deployment.l1Messenger.OTHER_MESSENGER() == 0x4200000000000000000000000000000000000007); - assert(deployment.l1MessengerImpl.OTHER_MESSENGER() == 0x4200000000000000000000000000000000000007); + function test_ProxyAdmin_addressManager_succeeds() external { + assertEq(address(deployment.proxyAdmin.addressManager()), address(deployment.addressManager)); } - function test_L1CrossDomainMessenger_PORTAL() external view { - assert(address(deployment.l1Messenger.PORTAL()) == address(deployment.portal)); - assert(address(deployment.l1MessengerImpl.PORTAL()) == address(deployment.portal)); + function test_ProxyAdmin_AddressManager_owner_succeeds() external { + assertEq(deployment.addressManager.owner(), address(deployment.proxyAdmin)); } /** - * @dev Tests for `L1StandardBridge` + * @dev Tests for `AddressManager` */ - function test_L1StandardBridge_MESSENGER() external view { - assert(address(deployment.l1ERC20Bridge.MESSENGER()) == address(deployment.l1Messenger)); - assert(address(deployment.l1ERC20BridgeImpl.MESSENGER()) == address(deployment.l1Messenger)); - } - - function test_L1StandardBridge_OTHER_BRIDGE() external view { - assert(address(deployment.l1ERC20Bridge.OTHER_BRIDGE()) == 0x4200000000000000000000000000000000000010); - assert(address(deployment.l1ERC20BridgeImpl.OTHER_BRIDGE()) == 0x4200000000000000000000000000000000000010); - } - - function test_L1StandardBridge_depositETH() external { - vm.prank(alice); - deployment.l1ERC20Bridge.depositETH{ value: 1 ether }(50000, hex""); + function test_AddressManager_owner() external view { + assert(deployment.addressManager.owner() == address(deployment.proxyAdmin)); } - /** - * @dev Tests for `L1ERC721Bridge` - */ - function test_L1ERC721Bridge_MESSENGER() external view { - assert(address(deployment.l1ERC721Bridge.MESSENGER()) == address(deployment.l1Messenger)); - assert(address(deployment.l1ERC721BridgeImpl.MESSENGER()) == address(deployment.l1Messenger)); + function test_AddressManager_getAddress_OVM_L1CrossDomainMessenger() external view { + assert( + deployment.addressManager.getAddress("OVM_L1CrossDomainMessenger") == address(deployment.l1MessengerImpl) + ); } - function test_L1ERC721Bridge_OTHER_BRIDGE() external view { - assert(address(deployment.l1ERC721Bridge.OTHER_BRIDGE()) == 0x6200000000000000000000000000000000000001); - assert(address(deployment.l1ERC721BridgeImpl.OTHER_BRIDGE()) == 0x6200000000000000000000000000000000000001); + function test_AddressManager_getAddress_DTL_SHUTOFF_BLOCK_succeeds() external { + assertEq(deployment.addressManager.getAddress("DTL_SHUTOFF_BLOCK"), address(uint160(block.number))); } - function test_L1ERC721Bridge_bridgeERC721() external { - TestERC721 local = new TestERC721(); - TestERC721 remote = new TestERC721(); - uint256 tokenId = 1337; - - local.mint(alice, tokenId); - - vm.prank(alice); - local.approve(address(deployment.l1ERC721Bridge), tokenId); - - vm.prank(alice); - deployment.l1ERC721Bridge.bridgeERC721(address(local), address(remote), tokenId, 50000, hex""); + function test_AddressManager_getAddress_OVM_CanonicalTransactionChain_succeeds() external { + // legacy addresses are expected to be set zero value + assertEq(deployment.addressManager.getAddress("OVM_CanonicalTransactionChain"), address(0)); } /** - * @dev Tests for `ProtocolVersions` + * @dev Tests OAS/ERC20/ERC721 balance migration */ - function test_ProtocolVersions_initialized() external { - vm.expectRevert("Initializable: contract is already initialized"); - deployment.protocolVersions.initialize(address(0), ProtocolVersion.wrap(0), ProtocolVersion.wrap(0)); + function test_OAS_migration_succeeds() external { + // OAS expected to be transferred to OasysPortal + assertEq(address(deployment.portal).balance, depositedAmount); } - function test_ProtocolVersions_owner() external view { - assert(deployment.protocolVersions.owner() == deployment.buildCfg.finalSystemOwner); - assert(deployment.protocolVersionsImpl.owner() == address(0xdEaD)); - } - - function test_ProtocolVersions_required() external view { - assert(ProtocolVersion.unwrap(deployment.protocolVersions.required()) == 0); - assert(ProtocolVersion.unwrap(deployment.protocolVersionsImpl.required()) == 0); + function test_ERC20_migration_succeeds() external { + assertEq(l1ERC20.balanceOf(address(deployment.l1ERC20Bridge)), depositedAmount); + assertEq( + L1StandardBridge(deployment.l1ERC20Bridge).deposits(address(l1ERC20), address(l2ERC20)), depositedAmount + ); } - function test_ProtocolVersions_recommended() external view { - assert(ProtocolVersion.unwrap(deployment.protocolVersions.recommended()) == 0); - assert(ProtocolVersion.unwrap(deployment.protocolVersionsImpl.recommended()) == 0); + function test_ERC721_migration_succeeds() external { + assertEq(l1ERC721.ownerOf(depositedTokenId), address(deployment.l1ERC721Bridge)); + assertEq( + L1ERC721Bridge(deployment.l1ERC721Bridge).deposits(address(l1ERC721), address(l2ERC721), depositedTokenId), + true + ); } } diff --git a/packages/contracts-bedrock/test/setup/oasys/MockLegacyL1BuildAgent.sol b/packages/contracts-bedrock/test/setup/oasys/MockLegacyL1BuildAgent.sol new file mode 100644 index 000000000..8d1101e51 --- /dev/null +++ b/packages/contracts-bedrock/test/setup/oasys/MockLegacyL1BuildAgent.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +contract MockLegacyL1BuildAgent { + mapping(uint256 => address) public addressManagers; + address[] private _builders; + uint256[] private _chainIds; + + function setAddressManager(uint256 _chainId, address _addressManager) public { + addressManagers[_chainId] = _addressManager; + } + + function getAddressManager(uint256 _chainId) public view returns (address) { + return addressManagers[_chainId]; + } + + function setBuilder(address _builder, uint256 _chainId) public { + _builders.push(_builder); + _chainIds.push(_chainId); + } + + function getBuilts( + uint256 cursor, + uint256 howMany + ) + external + view + returns (address[] memory, uint256[] memory, uint256) + { + uint256 length = _builders.length; + if (cursor + howMany >= length) { + howMany = length - cursor; + } + address[] memory builders = new address[](howMany); + uint256[] memory chainIds = new uint256[](howMany); + for (uint256 i = 0; i < howMany; i++) { + builders[i] = _builders[cursor + i]; + chainIds[i] = _chainIds[cursor + i]; + } + return (builders, chainIds, cursor + howMany); + } +} diff --git a/packages/contracts-bedrock/test/setup/oasys/MockLegacyL1BuildDeposit.sol b/packages/contracts-bedrock/test/setup/oasys/MockLegacyL1BuildDeposit.sol new file mode 100644 index 000000000..3bfb4d069 --- /dev/null +++ b/packages/contracts-bedrock/test/setup/oasys/MockLegacyL1BuildDeposit.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +contract MockLegacyL1BuildDeposit { + mapping(address => uint256) public _buildBlock; + + function setBuildBlock(address _builder, uint256 blockNumber) public { + _buildBlock[_builder] = blockNumber; + } + + function getBuildBlock(address _builder) public view returns (uint256) { + return _buildBlock[_builder]; + } +} diff --git a/packages/contracts-bedrock/test/setup/oasys/MockLegacyL1ERC721Bridge.sol b/packages/contracts-bedrock/test/setup/oasys/MockLegacyL1ERC721Bridge.sol new file mode 100644 index 000000000..0926e7c8f --- /dev/null +++ b/packages/contracts-bedrock/test/setup/oasys/MockLegacyL1ERC721Bridge.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; + +contract MockLegacyL1ERC721Bridge { + address public messenger; + address public l2ERC721Bridge; + mapping(address => mapping(address => mapping(uint256 => bool))) public deposits; + + function depositERC721( + address _l1Token, + address _l2Token, + uint256 _tokenId, + uint32, /*_l2Gas*/ + bytes calldata /*_data*/ + ) + external + { + deposits[_l1Token][_l2Token][_tokenId] = true; + IERC721(_l1Token).transferFrom(msg.sender, address(this), _tokenId); + } +} diff --git a/packages/contracts-bedrock/test/setup/oasys/MockLegacyL1StandardBridge.sol b/packages/contracts-bedrock/test/setup/oasys/MockLegacyL1StandardBridge.sol new file mode 100644 index 000000000..4f801c2fa --- /dev/null +++ b/packages/contracts-bedrock/test/setup/oasys/MockLegacyL1StandardBridge.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +contract MockLegacyL1StandardBridge { + address public messenger; + address public l2TokenBridge; + mapping(address => mapping(address => uint256)) public deposits; + + function depositETH(uint32, /*_l2Gas*/ bytes calldata /*_data*/ ) external payable { } + + function depositERC20( + address _l1Token, + address _l2Token, + uint256 _amount, + uint32, /*_l2Gas*/ + bytes calldata /*_data*/ + ) + external + { + deposits[_l1Token][_l2Token] = deposits[_l1Token][_l2Token] + _amount; + IERC20(_l1Token).transferFrom(msg.sender, address(this), _amount); + } +} diff --git a/packages/contracts-bedrock/test/setup/oasys/SetupL1BuildAgent.sol b/packages/contracts-bedrock/test/setup/oasys/SetupL1BuildAgent.sol index ac2a782c7..2f8c366ce 100644 --- a/packages/contracts-bedrock/test/setup/oasys/SetupL1BuildAgent.sol +++ b/packages/contracts-bedrock/test/setup/oasys/SetupL1BuildAgent.sol @@ -14,6 +14,7 @@ import { IL1BuildAgent } from "src/oasys/L1/build/interfaces/IL1BuildAgent.sol"; import { L1BuildAgent } from "src/oasys/L1/build/L1BuildAgent.sol"; import { L1BuildDeposit } from "src/oasys/L1/build/L1BuildDeposit.sol"; +import { TestERC721 } from "test/mocks/TestERC721.sol"; import { Constants } from "src/libraries/Constants.sol"; import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; import { OasysPortal } from "src/oasys/L1/messaging/OasysPortal.sol"; @@ -23,6 +24,7 @@ import { ResourceMetering } from "src/L1/ResourceMetering.sol"; import { L1CrossDomainMessenger } from "src/L1/L1CrossDomainMessenger.sol"; import { L1StandardBridge } from "src/L1/L1StandardBridge.sol"; import { L1ERC721Bridge } from "src/L1/L1ERC721Bridge.sol"; +import { ProtocolVersion } from "src/L1/ProtocolVersions.sol"; import { ProtocolVersions } from "src/L1/ProtocolVersions.sol"; import { AddressManager } from "src/legacy/AddressManager.sol"; import { OasysL2OutputOracleVerifier } from "src/oasys/L1/rollup/OasysL2OutputOracleVerifier.sol"; @@ -31,6 +33,8 @@ import { Path } from "scripts/oasys/L1/build/_path.sol"; import { Deploy } from "scripts/oasys/L1/build/Deploy.s.sol"; import { BuiltInContracts } from "test/setup/oasys/BuiltInContracts.sol"; +import { MockLegacyL1BuildAgent } from "test/setup/oasys/MockLegacyL1BuildAgent.sol"; +import { MockLegacyL1BuildDeposit } from "test/setup/oasys/MockLegacyL1BuildDeposit.sol"; contract SetupL1BuildAgent is Test { using stdJson for string; @@ -38,6 +42,7 @@ contract SetupL1BuildAgent is Test { struct Deployment { // Build config uint256 chainId; + AddressManager addressManager; IL1BuildAgent.BuildConfig buildCfg; // Deployed proxies OasysPortal portal; @@ -49,7 +54,6 @@ contract SetupL1BuildAgent is Test { ProtocolVersions protocolVersions; // Deployed implementations ProxyAdmin proxyAdmin; - AddressManager addressManager; OasysPortal portalImpl; OasysL2OutputOracle l2OracleImpl; SystemConfig systemConfigImpl; @@ -74,6 +78,7 @@ contract SetupL1BuildAgent is Test { address challenger = makeAddr("challenger"); address batcher = makeAddr("batcher"); address p2pSequencer = makeAddr("p2pSequencer"); + address relayer = makeAddr("messageRelayer"); // Dependency contracts IPermissionedContractFactory permissionedFactory; @@ -86,6 +91,10 @@ contract SetupL1BuildAgent is Test { /// @dev Default deployment L2 Deployment deployment; + /// Legacy contracts + MockLegacyL1BuildAgent legacyAgent; + MockLegacyL1BuildDeposit legacyDeposit; + function setUp() public virtual { _addBalanceToTestWallets(); @@ -95,61 +104,6 @@ contract SetupL1BuildAgent is Test { vm.prank(depositor); l1Deposit.deposit{ value: 1 ether }(builder); - - vm.prank(builder); - deployment = _runL1BuildAgent( - 5555, - IL1BuildAgent.BuildConfig({ - finalSystemOwner: finalOwner, - l2OutputOracleProposer: proposer, - l2OutputOracleChallenger: challenger, - batchSenderAddress: batcher, - p2pSequencerAddress: p2pSequencer, - l2BlockTime: 5, - l2GasLimit: 50_000_000, - l2OutputOracleSubmissionInterval: 50, - finalizationPeriodSeconds: 5 days, - l2OutputOracleStartingBlockNumber: 500, - l2OutputOracleStartingTimestamp: block.timestamp - }) - ); - - console.log("\nWallets"); - console.log("alice : %s", alice); - console.log("bob : %s", bob); - console.log("depositor : %s", depositor); - console.log("builder : %s", builder); - console.log("finalOwner : %s", finalOwner); - console.log("proposer : %s", proposer); - console.log("challenger : %s", challenger); - console.log("batcher : %s", batcher); - console.log("p2pSequencer : %s", p2pSequencer); - - console.log("\nDependency contracts"); - console.log("PermissionedContractFactory : %s", address(permissionedFactory)); - console.log("OasysL2OutputOracleVerifier : %s", address(l2OracleVerifier)); - - console.log("\nL1 Build contracts"); - console.log("L1BuildAgent : %s", address(l1Agent)); - console.log("L1BuildDeposit : %s", address(l1Deposit)); - - console.log("\nProxies"); - console.log("OasysPortal : %s", address(deployment.portal)); - console.log("OasysL2OutputOracle : %s", address(deployment.l2Oracle)); - console.log("SystemConfig : %s", address(deployment.systemConfig)); - console.log("L1CrossDomainMessenger : %s", address(deployment.l1Messenger)); - console.log("L1StandardBridge : %s", address(deployment.l1ERC20Bridge)); - console.log("L1ERC721Bridge : %s", address(deployment.l1ERC721Bridge)); - console.log("ProtocolVersions : %s", address(deployment.protocolVersions)); - - console.log("\nImplementations"); - console.log("OasysPortal : %s", address(deployment.portalImpl)); - console.log("OasysL2OutputOracle : %s", address(deployment.l2OracleImpl)); - console.log("SystemConfig : %s", address(deployment.systemConfigImpl)); - console.log("L1CrossDomainMessenger : %s", address(deployment.l1MessengerImpl)); - console.log("L1StandardBridge : %s", address(deployment.l1ERC20BridgeImpl)); - console.log("L1ERC721Bridge : %s", address(deployment.l1ERC721BridgeImpl)); - console.log("ProtocolVersions : %s", address(deployment.protocolVersionsImpl)); } function _addBalanceToTestWallets() internal { @@ -172,10 +126,13 @@ contract SetupL1BuildAgent is Test { internal returns (L1BuildAgent, L1BuildDeposit, OasysL2OutputOracleVerifier) { + legacyAgent = new MockLegacyL1BuildAgent(); + legacyDeposit = new MockLegacyL1BuildDeposit(); + vm.setEnv("SALT", salt); vm.setEnv("PERMISSIONED_FACTORY", vm.toString(address(factory))); - vm.setEnv("LEGACY_AGENT", vm.toString(address(0))); - vm.setEnv("LEGACY_DEPOSIT", vm.toString(address(0))); + vm.setEnv("LEGACY_AGENT", vm.toString(address(legacyAgent))); + vm.setEnv("LEGACY_DEPOSIT", vm.toString(address(legacyDeposit))); string memory jsonPath = string.concat(Path.deployOutDir(), "/test.json"); @@ -202,34 +159,29 @@ contract SetupL1BuildAgent is Test { /// @dev Run `L1BuildAgent.build()` method. function _runL1BuildAgent( uint256 chainId, + address addressManager, IL1BuildAgent.BuildConfig memory cfg ) internal returns (Deployment memory) { - ( - address proxyAdmin, - address[7] memory proxys, - address[7] memory impls, - address batchInbox, - address addressManager - ) = l1Agent.build(chainId, cfg); + (IL1BuildAgent.BuiltAddressList memory results, address[7] memory impls) = l1Agent.build(chainId, cfg); return Deployment({ // Build config chainId: chainId, + addressManager: AddressManager(addressManager), buildCfg: cfg, // Deployed proxies - portal: OasysPortal(payable(proxys[0])), - l2Oracle: OasysL2OutputOracle(proxys[1]), - systemConfig: SystemConfig(proxys[2]), - l1Messenger: L1CrossDomainMessenger(proxys[3]), - l1ERC20Bridge: L1StandardBridge(payable(proxys[4])), - l1ERC721Bridge: L1ERC721Bridge(proxys[5]), - protocolVersions: ProtocolVersions(proxys[6]), + portal: OasysPortal(payable(results.oasysPortal)), + l2Oracle: OasysL2OutputOracle(results.oasysL2OutputOracle), + systemConfig: SystemConfig(results.systemConfig), + l1Messenger: L1CrossDomainMessenger(results.l1CrossDomainMessenger), + l1ERC20Bridge: L1StandardBridge(payable(results.l1StandardBridge)), + l1ERC721Bridge: L1ERC721Bridge(results.l1ERC721Bridge), + protocolVersions: ProtocolVersions(results.protocolVersions), // Deployed implementations - proxyAdmin: ProxyAdmin(proxyAdmin), - addressManager: AddressManager(addressManager), + proxyAdmin: ProxyAdmin(results.proxyAdmin), portalImpl: OasysPortal(payable(impls[0])), l2OracleImpl: OasysL2OutputOracle(impls[1]), systemConfigImpl: SystemConfig(impls[2]), @@ -238,7 +190,291 @@ contract SetupL1BuildAgent is Test { l1ERC721BridgeImpl: L1ERC721Bridge(impls[5]), protocolVersionsImpl: ProtocolVersions(impls[6]), // Misc - batchInbox: batchInbox + batchInbox: results.batchInbox }); } } + +contract L1BuildAgentTestCommon is SetupL1BuildAgent { + function test_batchInbox() external view { + assert(deployment.batchInbox == 0xfF00000000000000000000000000000015B3FF00); + } + + /** + * @dev Tests for `ProxyAdmin` + */ + function test_ProxyAdmin_owner() external view { + assert(deployment.proxyAdmin.owner() == deployment.buildCfg.finalSystemOwner); + } + + function test_ProxyAdmin_getProxyAdmin_SystemConfig_succeeds() external { + assertEq( + deployment.proxyAdmin.getProxyAdmin(payable(address(deployment.systemConfig))), + address(deployment.proxyAdmin) + ); + } + + function test_ProxyAdmin_getProxyAdmin_OasysPortal_succeeds() external { + assertEq( + deployment.proxyAdmin.getProxyAdmin(payable(address(deployment.portal))), address(deployment.proxyAdmin) + ); + } + + function test_ProxyAdmin_getProxyAdmin_OasysL2OutputOracle_succeeds() external { + assertEq( + deployment.proxyAdmin.getProxyAdmin(payable(address(deployment.l2Oracle))), address(deployment.proxyAdmin) + ); + } + + function test_ProxyAdmin_getProxyAdmin_L1CrossDomainMessenger_succeeds() external { + assertEq( + deployment.proxyAdmin.getProxyAdmin(payable(address(deployment.l1Messenger))), + address(deployment.proxyAdmin) + ); + } + + function test_ProxyAdmin_getProxyAdmin_L1StandardBridge_succeeds() external { + assertEq( + deployment.proxyAdmin.getProxyAdmin(payable(address(deployment.l1ERC20Bridge))), + address(deployment.proxyAdmin) + ); + } + + function test_ProxyAdmin_getProxyAdmin_L1ERC721Bridge_succeeds() external { + assertEq( + deployment.proxyAdmin.getProxyAdmin(payable(address(deployment.l1ERC721Bridge))), + address(deployment.proxyAdmin) + ); + } + + function test_ProxyAdmin_getProxyAdmin_ProtocolVersions_succeeds() external { + assertEq( + deployment.proxyAdmin.getProxyAdmin(payable(address(deployment.protocolVersions))), + address(deployment.proxyAdmin) + ); + } + + /** + * @dev Tests for `OasysPortal` + */ + function test_OasysPortal_initialized() external { + vm.expectRevert("Initializable: contract is already initialized"); + deployment.portal.initialize(false); + } + + function test_OasysPortal_L2_ORACLE() external view { + assert(address(deployment.portal.L2_ORACLE()) == address(deployment.l2Oracle)); + assert(address(deployment.portalImpl.L2_ORACLE()) == address(deployment.l2Oracle)); + } + + function test_OasysPortal_SYSTEM_CONFIG() external view { + assert(address(deployment.portal.SYSTEM_CONFIG()) == address(deployment.systemConfig)); + assert(address(deployment.portalImpl.SYSTEM_CONFIG()) == address(deployment.systemConfig)); + } + + function test_OasysPortal_GUARDIAN() external view { + assert(address(deployment.portal.GUARDIAN()) == deployment.buildCfg.finalSystemOwner); + assert(address(deployment.portalImpl.GUARDIAN()) == deployment.buildCfg.finalSystemOwner); + } + + function test_OasysPortal_l2Sender() external view { + assert(deployment.portal.l2Sender() == 0x000000000000000000000000000000000000dEaD); + assert(deployment.portalImpl.l2Sender() == 0x000000000000000000000000000000000000dEaD); + } + + function test_OasysPortal_paused() external view { + assert(deployment.portal.paused() == false); + assert(deployment.portalImpl.paused() == true); + } + + function test_OasysPortal_messageRelayer() external view { + assert(deployment.portal.messageRelayer() == address(deployment.buildCfg.messageRelayer)); + assert(deployment.portalImpl.messageRelayer() == address(0)); + } + + /** + * @dev Tests for `OasysL2OutputOracle` + */ + function test_OasysL2OutputOracle_initialized() external { + vm.expectRevert("Initializable: contract is already initialized"); + deployment.l2Oracle.initialize(0, 0); + } + + function test_OasysL2OutputOracle_SUBMISSION_INTERVAL() external view { + assert(deployment.l2Oracle.SUBMISSION_INTERVAL() == deployment.buildCfg.l2OutputOracleSubmissionInterval); + assert(deployment.l2OracleImpl.SUBMISSION_INTERVAL() == deployment.buildCfg.l2OutputOracleSubmissionInterval); + } + + function test_OasysL2OutputOracle_L2_BLOCK_TIME() external view { + assert(deployment.l2Oracle.L2_BLOCK_TIME() == deployment.buildCfg.l2BlockTime); + assert(deployment.l2OracleImpl.L2_BLOCK_TIME() == deployment.buildCfg.l2BlockTime); + } + + function test_OasysL2OutputOracle_CHALLENGER() external view { + assert(deployment.l2Oracle.CHALLENGER() == deployment.buildCfg.l2OutputOracleChallenger); + assert(deployment.l2OracleImpl.CHALLENGER() == deployment.buildCfg.l2OutputOracleChallenger); + } + + function testOasysL2OutputOracle__PROPOSER() external view { + assert(deployment.l2Oracle.PROPOSER() == deployment.buildCfg.l2OutputOracleProposer); + assert(deployment.l2OracleImpl.PROPOSER() == deployment.buildCfg.l2OutputOracleProposer); + } + + function test_OasysL2OutputOracle_FINALIZATION_PERIOD_SECONDS() external view { + assert(deployment.l2Oracle.FINALIZATION_PERIOD_SECONDS() == deployment.buildCfg.finalizationPeriodSeconds); + assert(deployment.l2OracleImpl.FINALIZATION_PERIOD_SECONDS() == deployment.buildCfg.finalizationPeriodSeconds); + } + + function test_OasysL2OutputOracle_VERIFIER() external view { + assert(address(deployment.l2Oracle.VERIFIER()) == address(l2OracleVerifier)); + assert(address(deployment.l2OracleImpl.VERIFIER()) == address(l2OracleVerifier)); + } + + function test_OasysL2OutputOracle_startingBlockNumber() external view { + assert(deployment.l2Oracle.startingBlockNumber() == deployment.buildCfg.l2OutputOracleStartingBlockNumber); + assert(deployment.l2OracleImpl.startingBlockNumber() == 0); + } + + function test_OasysL2OutputOracle_startingTimestamp() external view { + assert(deployment.l2Oracle.startingTimestamp() == deployment.buildCfg.l2OutputOracleStartingTimestamp); + assert(deployment.l2OracleImpl.startingTimestamp() == 0); + } + + /** + * @dev Tests for `SystemConfig` + */ + function test_SystemConfig_initialized() external { + ResourceMetering.ResourceConfig memory cfg; + vm.expectRevert("Initializable: contract is already initialized"); + deployment.systemConfig.initialize(address(0), 0, 0, bytes32(0), 0, address(0), cfg); + } + + function test_SystemConfig_owner() external view { + assert(deployment.systemConfig.owner() == deployment.buildCfg.finalSystemOwner); + assert(deployment.systemConfigImpl.owner() == address(0xdEaD)); + } + + function test_SystemConfig_overhead() external view { + assert(deployment.systemConfig.overhead() == 188); + assert(deployment.systemConfigImpl.overhead() == 0); + } + + function test_SystemConfig_scalar() external view { + assert(deployment.systemConfig.scalar() == 684_000); + assert(deployment.systemConfigImpl.scalar() == 0); + } + + function test_SystemConfig_batcherHash() external view { + bytes32 expect = bytes32(uint256(uint160(deployment.buildCfg.batchSenderAddress))); + assert(deployment.systemConfig.batcherHash() == expect); + assert(deployment.systemConfigImpl.batcherHash() == bytes32(0)); + } + + function test_SystemConfig_gasLimit() external view { + assert(deployment.systemConfig.gasLimit() == deployment.buildCfg.l2GasLimit); + assert(deployment.systemConfigImpl.gasLimit() == 20_000_000 + 1_000_000); + } + + function test_SystemConfig_resourceConfig() external view { + ResourceMetering.ResourceConfig memory expect = Constants.DEFAULT_RESOURCE_CONFIG(); + ResourceMetering.ResourceConfig memory actual = deployment.systemConfig.resourceConfig(); + + assert(actual.maxResourceLimit == expect.maxResourceLimit); + assert(actual.elasticityMultiplier == expect.elasticityMultiplier); + assert(actual.baseFeeMaxChangeDenominator == expect.baseFeeMaxChangeDenominator); + assert(actual.minimumBaseFee == expect.minimumBaseFee); + assert(actual.systemTxMaxGas == expect.systemTxMaxGas); + assert(actual.maximumBaseFee == expect.maximumBaseFee); + } + + function test_SystemConfig_unsafeBlockSigner() external view { + assert(deployment.systemConfig.unsafeBlockSigner() == deployment.buildCfg.p2pSequencerAddress); + assert(deployment.systemConfigImpl.unsafeBlockSigner() == address(0)); + } + + /** + * @dev Tests for `L1CrossDomainMessenger` + */ + function test_L1CrossDomainMessenger_initialized() external { + vm.expectRevert("Initializable: contract is already initialized"); + deployment.l1Messenger.initialize(); + } + + function test_L1CrossDomainMessenger_OTHER_MESSENGER() external view { + assert(deployment.l1Messenger.OTHER_MESSENGER() == 0x4200000000000000000000000000000000000007); + assert(deployment.l1MessengerImpl.OTHER_MESSENGER() == 0x4200000000000000000000000000000000000007); + } + + function test_L1CrossDomainMessenger_PORTAL() external view { + assert(address(deployment.l1Messenger.PORTAL()) == address(deployment.portal)); + assert(address(deployment.l1MessengerImpl.PORTAL()) == address(deployment.portal)); + } + + /** + * @dev Tests for `L1StandardBridge` + */ + function test_L1StandardBridge_MESSENGER() external view { + assert(address(deployment.l1ERC20Bridge.MESSENGER()) == address(deployment.l1Messenger)); + assert(address(deployment.l1ERC20BridgeImpl.MESSENGER()) == address(deployment.l1Messenger)); + } + + function test_L1StandardBridge_OTHER_BRIDGE() external view { + assert(address(deployment.l1ERC20Bridge.OTHER_BRIDGE()) == 0x4200000000000000000000000000000000000010); + assert(address(deployment.l1ERC20BridgeImpl.OTHER_BRIDGE()) == 0x4200000000000000000000000000000000000010); + } + + function test_L1StandardBridge_depositETH() external { + vm.prank(alice); + deployment.l1ERC20Bridge.depositETH{ value: 1 ether }(50000, hex""); + } + + /** + * @dev Tests for `L1ERC721Bridge` + */ + function test_L1ERC721Bridge_MESSENGER() external view { + assert(address(deployment.l1ERC721Bridge.MESSENGER()) == address(deployment.l1Messenger)); + assert(address(deployment.l1ERC721BridgeImpl.MESSENGER()) == address(deployment.l1Messenger)); + } + + function test_L1ERC721Bridge_OTHER_BRIDGE() external view { + assert(address(deployment.l1ERC721Bridge.OTHER_BRIDGE()) == 0x6200000000000000000000000000000000000001); + assert(address(deployment.l1ERC721BridgeImpl.OTHER_BRIDGE()) == 0x6200000000000000000000000000000000000001); + } + + function test_L1ERC721Bridge_bridgeERC721() external { + TestERC721 local = new TestERC721(); + TestERC721 remote = new TestERC721(); + uint256 tokenId = 1337; + + local.mint(alice, tokenId); + + vm.prank(alice); + local.approve(address(deployment.l1ERC721Bridge), tokenId); + + vm.prank(alice); + deployment.l1ERC721Bridge.bridgeERC721(address(local), address(remote), tokenId, 50000, hex""); + } + + /** + * @dev Tests for `ProtocolVersions` + */ + function test_ProtocolVersions_initialized() external { + vm.expectRevert("Initializable: contract is already initialized"); + deployment.protocolVersions.initialize(address(0), ProtocolVersion.wrap(0), ProtocolVersion.wrap(0)); + } + + function test_ProtocolVersions_owner() external view { + assert(deployment.protocolVersions.owner() == deployment.buildCfg.finalSystemOwner); + assert(deployment.protocolVersionsImpl.owner() == address(0xdEaD)); + } + + function test_ProtocolVersions_required() external view { + assert(ProtocolVersion.unwrap(deployment.protocolVersions.required()) == 0); + assert(ProtocolVersion.unwrap(deployment.protocolVersionsImpl.required()) == 0); + } + + function test_ProtocolVersions_recommended() external view { + assert(ProtocolVersion.unwrap(deployment.protocolVersions.recommended()) == 0); + assert(ProtocolVersion.unwrap(deployment.protocolVersionsImpl.recommended()) == 0); + } +}