diff --git a/packages/contracts-bedrock/src/L2/SuperchainERC20.sol b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol index 2aa6f420f4a..cacccde97ec 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol @@ -33,8 +33,8 @@ contract SuperchainERC20 is ISuperchainERC20, ERC20, ISemver { /// @notice Decimals of the token uint8 private immutable DECIMALS; - /// @notice Address of the corresponding version of this token on the L1. - address public immutable L1_TOKEN; + /// @notice Address of the corresponding version of this token on the remote chain. + address public immutable REMOTE_TOKEN; /// @notice Emitted whenever tokens are minted for an account. /// @param account Address of the account tokens are being minted for. @@ -68,12 +68,19 @@ contract SuperchainERC20 is ISuperchainERC20, ERC20, ISemver { /// @custom:semver 1.0.0 string public constant version = "1.0.0"; - /// @param _l1Token Address of the corresponding L1 token. - /// @param _name ERC20 name. - /// @param _symbol ERC20 symbol. - /// @param _decimals ERC20 decimals. - constructor(address _l1Token, string memory _name, string memory _symbol, uint8 _decimals) ERC20(_name, _symbol) { - L1_TOKEN = _l1Token; + /// @param _remoteToken Address of the corresponding remote token. + /// @param _name ERC20 name. + /// @param _symbol ERC20 symbol. + /// @param _decimals ERC20 decimals. + constructor( + address _remoteToken, + string memory _name, + string memory _symbol, + uint8 _decimals + ) + ERC20(_name, _symbol) + { + REMOTE_TOKEN = _remoteToken; DECIMALS = _decimals; } diff --git a/packages/contracts-bedrock/src/L2/SuperchainERC20Factory.sol b/packages/contracts-bedrock/src/L2/SuperchainERC20Factory.sol new file mode 100644 index 00000000000..c102ed0654f --- /dev/null +++ b/packages/contracts-bedrock/src/L2/SuperchainERC20Factory.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import { ISemver } from "src/universal/ISemver.sol"; +import { Predeploys } from "src/libraries/Predeploys.sol"; +import { BeaconProxy } from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol"; +import { CREATE3 } from "@rari-capital/solmate/src/utils/CREATE3.sol"; + +/// @custom:proxied +/// @title SuperchainERC20Factory +/// @notice SuperchainERC20Factory is a factory contract that deploys SuperchainERC20 Beacon Proxies using CREATE3. +contract SuperchainERC20Factory is ISemver { + /// @notice Mapping of the deployed SuperchainERC20 to the remote token address. + mapping(address superchainToken => address remoteToken) public deployments; + + /// @notice Emitted when a SuperchainERC20 is deployed. + /// @param superchainERC20 Address of the SuperchainERC20 deployment. + /// @param remoteToken Address of the remote token. + /// @param name Name of the SuperchainERC20. + /// @param symbol Symbol of the SuperchainERC20. + /// @param decimals Decimals of the SuperchainERC20. + event SuperchainERC20Deployed( + address indexed superchainERC20, address indexed remoteToken, string name, string symbol, uint8 decimals + ); + + /// @notice Semantic version. + /// @custom:semver 1.0.0 + string public constant version = "1.0.0"; + + /// @notice Deploys a SuperchainERC20 Beacon Proxy using CREATE3. + /// @param _remoteToken Address of the remote token. + /// @param _name Name of the SuperchainERC20. + /// @param _symbol Symbol of the SuperchainERC20. + /// @param _decimals Decimals of the SuperchainERC20. + /// @return _superchainERC20 Address of the SuperchainERC20 deployment. + function deploy( + address _remoteToken, + string memory _name, + string memory _symbol, + uint8 _decimals + ) + external + returns (address _superchainERC20) + { + // Encode the BeaconProxy creation code with the beacon contract address and metadata + bytes memory _creationCode = abi.encodePacked( + type(BeaconProxy).creationCode, + abi.encode(Predeploys.SUPERCHAIN_ERC20_BEACON, abi.encode(_remoteToken, _name, _symbol, _decimals)) + ); + + // Use CREATE3 for deterministic deployment + bytes32 _salt = keccak256(abi.encode(_remoteToken, _name, _symbol, _decimals)); + _superchainERC20 = CREATE3.deploy({ salt: _salt, creationCode: _creationCode, value: 0 }); + + // Store SuperchainERC20 and remote token addresses + deployments[_superchainERC20] = _remoteToken; + + emit SuperchainERC20Deployed(_superchainERC20, _remoteToken, _name, _symbol, _decimals); + } +} diff --git a/packages/contracts-bedrock/src/libraries/Predeploys.sol b/packages/contracts-bedrock/src/libraries/Predeploys.sol index d37dbca6af1..6f2b177f4fa 100644 --- a/packages/contracts-bedrock/src/libraries/Predeploys.sol +++ b/packages/contracts-bedrock/src/libraries/Predeploys.sol @@ -89,6 +89,10 @@ library Predeploys { /// @notice Address of the L2ToL2CrossDomainMessenger predeploy. address internal constant L2_TO_L2_CROSS_DOMAIN_MESSENGER = 0x4200000000000000000000000000000000000023; + /// TODO: Replace with real predeploy address + /// @notice Address of the SuperchainERC20Beacon predeploy. + address internal constant SUPERCHAIN_ERC20_BEACON = 0x4200000000000000000000000000000000000024; + /// @notice Returns the name of the predeploy at the given address. function getName(address _addr) internal pure returns (string memory out_) { require(isPredeployNamespace(_addr), "Predeploys: address must be a predeploy"); @@ -115,6 +119,7 @@ library Predeploys { if (_addr == LEGACY_ERC20_ETH) return "LegacyERC20ETH"; if (_addr == CROSS_L2_INBOX) return "CrossL2Inbox"; if (_addr == L2_TO_L2_CROSS_DOMAIN_MESSENGER) return "L2ToL2CrossDomainMessenger"; + if (_addr == SUPERCHAIN_ERC20_BEACON) return "SuperchainERC20Beacon"; revert("Predeploys: unnamed predeploy"); } @@ -131,7 +136,8 @@ library Predeploys { || _addr == L2_ERC721_BRIDGE || _addr == L1_BLOCK_ATTRIBUTES || _addr == L2_TO_L1_MESSAGE_PASSER || _addr == OPTIMISM_MINTABLE_ERC721_FACTORY || _addr == PROXY_ADMIN || _addr == BASE_FEE_VAULT || _addr == L1_FEE_VAULT || _addr == SCHEMA_REGISTRY || _addr == EAS || _addr == GOVERNANCE_TOKEN - || (_useInterop && _addr == CROSS_L2_INBOX) || (_useInterop && _addr == L2_TO_L2_CROSS_DOMAIN_MESSENGER); + || (_useInterop && _addr == CROSS_L2_INBOX) || (_useInterop && _addr == L2_TO_L2_CROSS_DOMAIN_MESSENGER) + || (_useInterop && _addr == SUPERCHAIN_ERC20_BEACON); } function isPredeployNamespace(address _addr) internal pure returns (bool) { diff --git a/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol index 7df78cd845d..e5e4f8828ae 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol @@ -26,7 +26,7 @@ import { ISuperchainERC20 } from "src/L2/ISuperchainERC20.sol"; /// @dev Contract for testing the SuperchainERC20 contract. contract SuperchainERC20Test is Test { address internal constant ZERO_ADDRESS = address(0); - address internal constant L1_TOKEN = address(0x123); + address internal constant REMOTE_TOKEN = address(0x123); string internal constant NAME = "SuperchainERC20"; string internal constant SYMBOL = "SCE"; uint8 internal constant DECIMALS = 18; @@ -37,7 +37,7 @@ contract SuperchainERC20Test is Test { /// @dev Sets up the test suite. function setUp() public { - superchainERC20 = new SuperchainERC20(L1_TOKEN, NAME, SYMBOL, DECIMALS); + superchainERC20 = new SuperchainERC20(REMOTE_TOKEN, NAME, SYMBOL, DECIMALS); } /// @dev Helper function to setup a mock and expect a call to it. @@ -51,7 +51,7 @@ contract SuperchainERC20Test is Test { assertEq(superchainERC20.name(), NAME); assertEq(superchainERC20.symbol(), SYMBOL); assertEq(superchainERC20.decimals(), DECIMALS); - assertEq(superchainERC20.L1_TOKEN(), L1_TOKEN); + assertEq(superchainERC20.REMOTE_TOKEN(), REMOTE_TOKEN); } /// @dev Tests the `mint` function reverts when the caller is not the bridge. @@ -286,13 +286,13 @@ contract SuperchainERC20Test is Test { /// @dev Tests the `decimals` function always returns the correct value. function testFuzz_decimals_succeeds(uint8 _decimals) public { - SuperchainERC20 _newSuperchainERC20 = new SuperchainERC20(L1_TOKEN, NAME, SYMBOL, _decimals); + SuperchainERC20 _newSuperchainERC20 = new SuperchainERC20(REMOTE_TOKEN, NAME, SYMBOL, _decimals); assertEq(_newSuperchainERC20.decimals(), _decimals); } - /// @dev Tests the `L1_TOKEN` function always returns the correct value. - function testFuzz_l1Token_succeeds(address _l1Token) public { - SuperchainERC20 _newSuperchainERC20 = new SuperchainERC20(_l1Token, NAME, SYMBOL, DECIMALS); - assertEq(_newSuperchainERC20.L1_TOKEN(), _l1Token); + /// @dev Tests the `REMOTE_TOKEN` function always returns the correct value. + function testFuzz_remoteToken_succeeds(address _remoteToken) public { + SuperchainERC20 _newSuperchainERC20 = new SuperchainERC20(_remoteToken, NAME, SYMBOL, DECIMALS); + assertEq(_newSuperchainERC20.REMOTE_TOKEN(), _remoteToken); } }