diff --git a/packages/contracts-bedrock/scripts/Artifacts.s.sol b/packages/contracts-bedrock/scripts/Artifacts.s.sol index 350d3d99ae68..bfac0c367bfc 100644 --- a/packages/contracts-bedrock/scripts/Artifacts.s.sol +++ b/packages/contracts-bedrock/scripts/Artifacts.s.sol @@ -154,6 +154,10 @@ abstract contract Artifacts { return payable(Predeploys.SCHEMA_REGISTRY); } else if (digest == keccak256(bytes("EAS"))) { return payable(Predeploys.EAS); + } else if (digest == keccak256(bytes("OptimismSuperchainERC20Factory"))) { + return payable(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY); + } else if (digest == keccak256(bytes("OptimismSuperchainERC20Beacon"))) { + return payable(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON); } return payable(address(0)); } diff --git a/packages/contracts-bedrock/scripts/L2Genesis.s.sol b/packages/contracts-bedrock/scripts/L2Genesis.s.sol index 1ab494b052f8..5151051520b4 100644 --- a/packages/contracts-bedrock/scripts/L2Genesis.s.sol +++ b/packages/contracts-bedrock/scripts/L2Genesis.s.sol @@ -262,6 +262,8 @@ contract L2Genesis is Deployer { setL2ToL2CrossDomainMessenger(); // 23 setSuperchainWETH(); // 24 setETHLiquidity(); // 25 + setOptimismSuperchainERC20Factory(); // 26 + setOptimismSuperchainERC20Beacon(); // 27 } } @@ -513,6 +515,18 @@ contract L2Genesis is Deployer { _setImplementationCode(Predeploys.SUPERCHAIN_WETH); } + /// @notice This predeploy is following the safety invariant #1. + /// This contract has no initializer. + function setOptimismSuperchainERC20Factory() internal { + _setImplementationCode(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY); + } + + /// @notice This predeploy is following the safety invariant #1. + /// This contract has no initializer. + function setOptimismSuperchainERC20Beacon() internal { + _setImplementationCode(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON); + } + /// @notice Sets all the preinstalls. /// Warning: the creator-accounts of the preinstall contracts have 0 nonce values. /// When performing a regular user-initiated contract-creation of a preinstall, diff --git a/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol b/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol index 57c95147314d..44bc584ddbb9 100644 --- a/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol +++ b/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol @@ -19,9 +19,6 @@ import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol import { IOptimismMintableERC20 } from "src/universal/interfaces/IOptimismMintableERC20.sol"; import { ILegacyMintableERC20 } from "src/universal/OptimismMintableERC20.sol"; -// TODO: Replace Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY with optimismSuperchainERC20Factory -import { Predeploys } from "src/libraries/Predeploys.sol"; - contract L2StandardBridgeInterop_Test is Bridge_Initializer { /// @notice Emitted when a conversion is made. event Converted(address indexed from, address indexed to, address indexed caller, uint256 amount); @@ -146,7 +143,7 @@ contract L2StandardBridgeInterop_LegacyToSuper_Test is L2StandardBridgeInterop_T _mockDeployments(address(l2OptimismMintableERC20Factory), _from, _remoteToken); // Mock the superchain factory to return address(0) - _mockDeployments(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY, _to, address(0)); + _mockDeployments(address(l2OptimismSuperchainERC20Factory), _to, address(0)); // Expect the revert with `InvalidSuperchainERC20Address` selector vm.expectRevert(InvalidSuperchainERC20Address.selector); @@ -177,7 +174,7 @@ contract L2StandardBridgeInterop_LegacyToSuper_Test is L2StandardBridgeInterop_T _mockDeployments(address(l2OptimismMintableERC20Factory), _from, _fromRemoteToken); // Mock the superchain factory to return `_toRemoteToken` - _mockDeployments(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY, _to, _toRemoteToken); + _mockDeployments(address(l2OptimismSuperchainERC20Factory), _to, _toRemoteToken); // Expect the revert with `InvalidTokenPair` selector vm.expectRevert(InvalidTokenPair.selector); @@ -204,7 +201,7 @@ contract L2StandardBridgeInterop_LegacyToSuper_Test is L2StandardBridgeInterop_T // Mock the legacy and superchain factory to return `_remoteToken` _mockDeployments(address(l2OptimismMintableERC20Factory), _from, _remoteToken); - _mockDeployments(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY, _to, _remoteToken); + _mockDeployments(address(l2OptimismSuperchainERC20Factory), _to, _remoteToken); // Expect the `Converted` event to be emitted vm.expectEmit(address(l2StandardBridge)); @@ -300,7 +297,7 @@ contract L2StandardBridgeInterop_SuperToLegacy_Test is L2StandardBridgeInterop_T _mockDeployments(address(l2OptimismMintableERC20Factory), _to, _remoteToken); // Mock the superchain factory to return address(0) - _mockDeployments(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY, _from, address(0)); + _mockDeployments(address(l2OptimismSuperchainERC20Factory), _from, address(0)); // Expect the revert with `InvalidSuperchainERC20Address` selector vm.expectRevert(InvalidSuperchainERC20Address.selector); @@ -331,7 +328,7 @@ contract L2StandardBridgeInterop_SuperToLegacy_Test is L2StandardBridgeInterop_T _mockDeployments(address(l2OptimismMintableERC20Factory), _to, _fromRemoteToken); // Mock the superchain factory to return `_toRemoteToken` - _mockDeployments(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY, _from, _toRemoteToken); + _mockDeployments(address(l2OptimismSuperchainERC20Factory), _from, _toRemoteToken); // Expect the revert with `InvalidTokenPair` selector vm.expectRevert(InvalidTokenPair.selector); @@ -358,7 +355,7 @@ contract L2StandardBridgeInterop_SuperToLegacy_Test is L2StandardBridgeInterop_T // Mock the legacy and superchain factory to return `_remoteToken` _mockDeployments(address(l2OptimismMintableERC20Factory), _to, _remoteToken); - _mockDeployments(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY, _from, _remoteToken); + _mockDeployments(address(l2OptimismSuperchainERC20Factory), _from, _remoteToken); // Expect the `Converted` event to be emitted vm.expectEmit(address(l2StandardBridge)); diff --git a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol index 0e82aca948a2..0fc7d88d0b7c 100644 --- a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.25; // Testing utilities import { Test } from "forge-std/Test.sol"; +import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; // Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; @@ -11,6 +12,8 @@ import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomai import { ERC1967Proxy } from "@openzeppelin/contracts-v5/proxy/ERC1967/ERC1967Proxy.sol"; import { Initializable } from "@openzeppelin/contracts-v5/proxy/utils/Initializable.sol"; import { IERC165 } from "@openzeppelin/contracts-v5/utils/introspection/IERC165.sol"; +import { IBeacon } from "@openzeppelin/contracts-v5/proxy/beacon/IBeacon.sol"; +import { BeaconProxy } from "@openzeppelin/contracts-v5/proxy/beacon/BeaconProxy.sol"; // Target contract import { @@ -40,9 +43,32 @@ contract OptimismSuperchainERC20Test is Test { /// @notice Sets up the test suite. function setUp() public { superchainERC20Impl = new OptimismSuperchainERC20(); + + // Deploy the OptimismSuperchainERC20Beacon contract + _deployBeacon(); + superchainERC20 = _deploySuperchainERC20Proxy(REMOTE_TOKEN, NAME, SYMBOL, DECIMALS); } + /// @notice Deploy the OptimismSuperchainERC20Beacon predeploy contract + function _deployBeacon() internal { + // Deploy the OptimismSuperchainERC20Beacon implementation + address _addr = Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON; + address _impl = Predeploys.predeployToCodeNamespace(_addr); + vm.etch(_impl, vm.getDeployedCode("OptimismSuperchainERC20Beacon.sol:OptimismSuperchainERC20Beacon")); + + // Deploy the ERC1967Proxy contract at the Predeploy + bytes memory code = vm.getDeployedCode("universal/Proxy.sol:Proxy"); + vm.etch(_addr, code); + EIP1967Helper.setAdmin(_addr, Predeploys.PROXY_ADMIN); + EIP1967Helper.setImplementation(_addr, _impl); + + // Mock implementation address + vm.mockCall( + _impl, abi.encodeWithSelector(IBeacon.implementation.selector), abi.encode(address(superchainERC20Impl)) + ); + } + /// @notice Helper function to deploy a proxy of the OptimismSuperchainERC20 contract. function _deploySuperchainERC20Proxy( address _remoteToken, @@ -55,9 +81,8 @@ contract OptimismSuperchainERC20Test is Test { { return OptimismSuperchainERC20( address( - // TODO: Use the SuperchainERC20 Beacon Proxy - new ERC1967Proxy( - address(superchainERC20Impl), + new BeaconProxy( + Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON, abi.encodeCall(OptimismSuperchainERC20.initialize, (_remoteToken, _name, _symbol, _decimals)) ) ) diff --git a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Factory.t.sol b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Factory.t.sol new file mode 100644 index 000000000000..b82a40bf2bc7 --- /dev/null +++ b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Factory.t.sol @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +// Testing utilities +import { Test } from "forge-std/Test.sol"; +import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; + +// Libraries +import { Predeploys } from "src/libraries/Predeploys.sol"; +import { CREATE3, Bytes32AddressLib } from "@rari-capital/solmate/src/utils/CREATE3.sol"; +import { IBeacon } from "@openzeppelin/contracts-v5/proxy/beacon/IBeacon.sol"; + +// Target contract +import { OptimismSuperchainERC20Factory, OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20Factory.sol"; + +/// @title OptimismSuperchainERC20FactoryTest +/// @notice Contract for testing the OptimismSuperchainERC20Factory contract. +contract OptimismSuperchainERC20FactoryTest is Test { + using Bytes32AddressLib for bytes32; + + OptimismSuperchainERC20 public superchainERC20Impl; + OptimismSuperchainERC20Factory public superchainERC20Factory; + + /// @notice Sets up the test suite. + function setUp() public { + superchainERC20Impl = new OptimismSuperchainERC20(); + + // Deploy the OptimismSuperchainERC20Beacon contract + _deployBeacon(); + + superchainERC20Factory = new OptimismSuperchainERC20Factory(); + } + + /// @notice Deploy the OptimismSuperchainERC20Beacon predeploy contract + function _deployBeacon() internal { + // Deploy the OptimismSuperchainERC20Beacon implementation + address _addr = Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON; + address _impl = Predeploys.predeployToCodeNamespace(_addr); + vm.etch(_impl, vm.getDeployedCode("OptimismSuperchainERC20Beacon.sol:OptimismSuperchainERC20Beacon")); + + // Deploy the ERC1967Proxy contract at the Predeploy + bytes memory code = vm.getDeployedCode("universal/Proxy.sol:Proxy"); + vm.etch(_addr, code); + EIP1967Helper.setAdmin(_addr, Predeploys.PROXY_ADMIN); + EIP1967Helper.setImplementation(_addr, _impl); + + // Mock implementation address + vm.mockCall( + _impl, abi.encodeWithSelector(IBeacon.implementation.selector), abi.encode(address(superchainERC20Impl)) + ); + } + + /// @notice Test that calling `deploy` with valid parameters succeeds. + function test_deploy_succeeds( + address _caller, + address _remoteToken, + string memory _name, + string memory _symbol, + uint8 _decimals + ) + public + { + // Arrange + bytes32 salt = keccak256(abi.encode(_remoteToken, _name, _symbol, _decimals)); + address deployment = _calculateTokenAddress(salt, address(superchainERC20Factory)); + + vm.expectEmit(true, true, true, true); + emit OptimismSuperchainERC20Factory.OptimismSuperchainERC20Created(deployment, _remoteToken, _caller); + + // Act + vm.prank(_caller); + address addr = superchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals); + + // Assert + assertTrue(addr == deployment); + assertTrue(OptimismSuperchainERC20(deployment).decimals() == _decimals); + assertTrue(OptimismSuperchainERC20(deployment).remoteToken() == _remoteToken); + assertEq(OptimismSuperchainERC20(deployment).name(), _name); + assertEq(OptimismSuperchainERC20(deployment).symbol(), _symbol); + assertEq(superchainERC20Factory.deployments(deployment), _remoteToken); + } + + /// @notice Test that calling `deploy` with the same parameters twice reverts. + function test_deploy_sameTwice_reverts( + address _caller, + address _remoteToken, + string memory _name, + string memory _symbol, + uint8 _decimals + ) + external + { + // Arrange + vm.prank(_caller); + superchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals); + + vm.expectRevert(bytes("DEPLOYMENT_FAILED")); + + // Act + vm.prank(_caller); + superchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals); + } + + /// @notice Precalculates the address of the token contract using CREATE3. + function _calculateTokenAddress(bytes32 _salt, address _deployer) internal pure returns (address) { + address proxy = + keccak256(abi.encodePacked(bytes1(0xFF), _deployer, _salt, CREATE3.PROXY_BYTECODE_HASH)).fromLast20Bytes(); + + return keccak256(abi.encodePacked(hex"d694", proxy, hex"01")).fromLast20Bytes(); + } +} diff --git a/packages/contracts-bedrock/test/L2Genesis.t.sol b/packages/contracts-bedrock/test/L2Genesis.t.sol index be5cc0c50edf..ee993fe1110c 100644 --- a/packages/contracts-bedrock/test/L2Genesis.t.sol +++ b/packages/contracts-bedrock/test/L2Genesis.t.sol @@ -150,8 +150,8 @@ contract L2GenesisTest is Test { // 2 predeploys do not have proxies assertEq(getCodeCount(_path, "Proxy.sol:Proxy"), Predeploys.PREDEPLOY_COUNT - 2); - // 22 proxies have the implementation set if useInterop is true and 17 if useInterop is false - assertEq(getPredeployCountWithSlotSet(_path, Constants.PROXY_IMPLEMENTATION_ADDRESS), _useInterop ? 22 : 17); + // 23 proxies have the implementation set if useInterop is true and 17 if useInterop is false + assertEq(getPredeployCountWithSlotSet(_path, Constants.PROXY_IMPLEMENTATION_ADDRESS), _useInterop ? 23 : 17); // All proxies except 2 have the proxy 1967 admin slot set to the proxy admin assertEq( diff --git a/packages/contracts-bedrock/test/setup/Setup.sol b/packages/contracts-bedrock/test/setup/Setup.sol index 5bfd5328144c..bd322118012f 100644 --- a/packages/contracts-bedrock/test/setup/Setup.sol +++ b/packages/contracts-bedrock/test/setup/Setup.sol @@ -109,6 +109,10 @@ contract Setup { SuperchainWETH superchainWeth = SuperchainWETH(payable(Predeploys.SUPERCHAIN_WETH)); ETHLiquidity ethLiquidity = ETHLiquidity(Predeploys.ETH_LIQUIDITY); + // TODO: Replace with OptimismSuperchainERC20Factory when updating pragmas + IOptimismERC20Factory l2OptimismSuperchainERC20Factory = + IOptimismERC20Factory(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY); + /// @dev Deploys the Deploy contract without including its bytecode in the bytecode /// of this contract by fetching the bytecode dynamically using `vm.getCode()`. /// If the Deploy bytecode is included in this contract, then it will double @@ -227,6 +231,8 @@ contract Setup { labelPredeploy(Predeploys.WETH); labelPredeploy(Predeploys.SUPERCHAIN_WETH); labelPredeploy(Predeploys.ETH_LIQUIDITY); + labelPredeploy(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY); + labelPredeploy(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON); // L2 Preinstalls labelPreinstall(Preinstalls.MultiCall3);