diff --git a/packages/contracts/contracts/L1/messaging/L1StandardTokenFactory.sol b/packages/contracts/contracts/L1/messaging/L1StandardTokenFactory.sol new file mode 100644 index 0000000000000..e879ccb93ebe7 --- /dev/null +++ b/packages/contracts/contracts/L1/messaging/L1StandardTokenFactory.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +/* Contract Imports */ +import { L1StandardERC20 } from "../../standards/L1StandardERC20.sol"; +import { Lib_PredeployAddresses } from "../../libraries/constants/Lib_PredeployAddresses.sol"; + +/** + * @title L1StandardTokenFactory + * @dev Factory contract for creating standard L1 token representations of L2 ERC20s + * compatible with and working on the standard bridge. + */ +contract L1StandardTokenFactory { + event StandardL1TokenCreated(address indexed _l2Token, address indexed _l1Token); + + /** + * @dev Creates an instance of the standard ERC20 token on L1. + * @param _l2Token Address of the corresponding L2 token. + * @param _name ERC20 name. + * @param _symbol ERC20 symbol. + */ + function createStandardL1Token( + address _l2Token, + string memory _name, + string memory _symbol + ) external { + require(_l2Token != address(0), "Must provide L2 token address"); + + L1StandardERC20 l1Token = new L1StandardERC20( + Lib_PredeployAddresses.L1_STANDARD_BRIDGE, + _l2Token, + _name, + _symbol + ); + + emit StandardL1TokenCreated(_l2Token, address(l1Token)); + } +} diff --git a/packages/contracts/contracts/standards/IL1StandardERC20.sol b/packages/contracts/contracts/standards/IL1StandardERC20.sol new file mode 100644 index 0000000000000..961ed3f8073c9 --- /dev/null +++ b/packages/contracts/contracts/standards/IL1StandardERC20.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; + +interface IL1StandardERC20 is IERC20, IERC165 { + function l2Token() external returns (address); + + function mint(address _to, uint256 _amount) external; + + function burn(address _from, uint256 _amount) external; + + event Mint(address indexed _account, uint256 _amount); + event Burn(address indexed _account, uint256 _amount); +} diff --git a/packages/contracts/contracts/standards/L1StandardERC20.sol b/packages/contracts/contracts/standards/L1StandardERC20.sol new file mode 100644 index 0000000000000..0d58e4d1c212f --- /dev/null +++ b/packages/contracts/contracts/standards/L1StandardERC20.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "./IL1StandardERC20.sol"; + +contract L1StandardERC20 is IL1StandardERC20, ERC20 { + address public l2Token; + address public l1Bridge; + + /** + * @param _l2Bridge Address of the L2 standard bridge. + * @param _l1Token Address of the corresponding L1 token. + * @param _name ERC20 name. + * @param _symbol ERC20 symbol. + */ + constructor( + address _l1Bridge, + address _l2Token, + string memory _name, + string memory _symbol + ) ERC20(_name, _symbol) { + l1Token = _l1Token; + l2Bridge = _l2Bridge; + } + + modifier onlyL1Bridge() { + require(msg.sender == l1Bridge, "Only L1 Bridge can mint and burn"); + _; + } + + // slither-disable-next-line external-function + function supportsInterface(bytes4 _interfaceId) public pure returns (bool) { + bytes4 firstSupportedInterface = bytes4(keccak256("supportsInterface(bytes4)")); // ERC165 + bytes4 secondSupportedInterface = IL1StandardERC20.l2Token.selector ^ + IL1StandardERC20.mint.selector ^ + IL1StandardERC20.burn.selector; + return _interfaceId == firstSupportedInterface || _interfaceId == secondSupportedInterface; + } + + // slither-disable-next-line external-function + function mint(address _to, uint256 _amount) public virtual onlyL1Bridge { + _mint(_to, _amount); + + emit Mint(_to, _amount); + } + + // slither-disable-next-line external-function + function burn(address _from, uint256 _amount) public virtual onlyL1Bridge { + _burn(_from, _amount); + + emit Burn(_from, _amount); + } +}