diff --git a/src/Optimism/IOptimismMintableERC20.sol b/src/Optimism/IOptimismMintableERC20.sol new file mode 100644 index 00000000..b261902c --- /dev/null +++ b/src/Optimism/IOptimismMintableERC20.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; + +/// @title IOptimismMintableERC20 +/// @notice This interface is available on the OptimismMintableERC20 contract. +/// We declare it as a separate interface so that it can be used in +/// custom implementations of OptimismMintableERC20. +interface IOptimismMintableERC20 is IERC165 { + function remoteToken() external view returns (address); + + function bridge() external returns (address); + + function mint(address _to, uint256 _amount) external; + + function burn(address _from, uint256 _amount) external; +} + +/// @custom:legacy +/// @title ILegacyMintableERC20 +/// @notice This interface was available on the legacy L2StandardERC20 contract. +/// It remains available on the OptimismMintableERC20 contract for +/// backwards compatibility. +interface ILegacyMintableERC20 is IERC165 { + function l1Token() external view returns (address); + + function mint(address _to, uint256 _amount) external; + + function burn(address _from, uint256 _amount) external; +} diff --git a/src/Optimism/OptimismMintableERC20.sol b/src/Optimism/OptimismMintableERC20.sol new file mode 100644 index 00000000..e4dc1b03 --- /dev/null +++ b/src/Optimism/OptimismMintableERC20.sol @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +import { ILegacyMintableERC20, IOptimismMintableERC20 } from "./IOptimismMintableERC20.sol"; +import { Semver } from "./Semver.sol"; + +/// @title OptimismMintableERC20 +/// @notice OptimismMintableERC20 is a standard extension of the base ERC20 token contract designed +/// to allow the StandardBridge contracts to mint and burn tokens. This makes it possible to +/// use an OptimismMintablERC20 as the L2 representation of an L1 token, or vice-versa. +/// Designed to be backwards compatible with the older StandardL2ERC20 token which was only +/// meant for use on L2. +contract OptimismMintableERC20 is IOptimismMintableERC20, ILegacyMintableERC20, ERC20, Semver { + /// @notice Address of the corresponding version of this token on the remote chain. + address public immutable REMOTE_TOKEN; + + /// @notice Address of the StandardBridge on this network. + address public immutable BRIDGE; + + /// @notice Decimals of the token + uint8 private immutable DECIMALS; + + /// @notice Emitted whenever tokens are minted for an account. + /// @param account Address of the account tokens are being minted for. + /// @param amount Amount of tokens minted. + event Mint(address indexed account, uint256 amount); + + /// @notice Emitted whenever tokens are burned from an account. + /// @param account Address of the account tokens are being burned from. + /// @param amount Amount of tokens burned. + event Burn(address indexed account, uint256 amount); + + /// @notice A modifier that only allows the bridge to call + modifier onlyBridge() { + require(msg.sender == BRIDGE, "OptimismMintableERC20: only bridge can mint and burn"); + _; + } + + /// @custom:semver 1.2.0 + /// @param _bridge Address of the L2 standard bridge. + /// @param _remoteToken Address of the corresponding L1 token. + /// @param _name ERC20 name. + /// @param _symbol ERC20 symbol. + constructor(address _bridge, address _remoteToken, string memory _name, string memory _symbol, uint8 _decimals) + ERC20(_name, _symbol) + Semver(1, 2, 0) + { + REMOTE_TOKEN = _remoteToken; + BRIDGE = _bridge; + DECIMALS = _decimals; + } + + /// @notice Allows the StandardBridge on this network to mint tokens. + /// @param _to Address to mint tokens to. + /// @param _amount Amount of tokens to mint. + function mint(address _to, uint256 _amount) external virtual override(IOptimismMintableERC20, ILegacyMintableERC20) onlyBridge { + _mint(_to, _amount); + emit Mint(_to, _amount); + } + + /// @notice Allows the StandardBridge on this network to burn tokens. + /// @param _from Address to burn tokens from. + /// @param _amount Amount of tokens to burn. + function burn(address _from, uint256 _amount) external virtual override(IOptimismMintableERC20, ILegacyMintableERC20) onlyBridge { + _burn(_from, _amount); + emit Burn(_from, _amount); + } + + /// @notice ERC165 interface check function. + /// @param _interfaceId Interface ID to check. + /// @return Whether or not the interface is supported by this contract. + function supportsInterface(bytes4 _interfaceId) external pure virtual returns (bool) { + bytes4 iface1 = type(IERC165).interfaceId; + // Interface corresponding to the legacy L2StandardERC20. + bytes4 iface2 = type(ILegacyMintableERC20).interfaceId; + // Interface corresponding to the updated OptimismMintableERC20 (this contract). + bytes4 iface3 = type(IOptimismMintableERC20).interfaceId; + return _interfaceId == iface1 || _interfaceId == iface2 || _interfaceId == iface3; + } + + /// @custom:legacy + /// @notice Legacy getter for the remote token. Use REMOTE_TOKEN going forward. + function l1Token() public view returns (address) { + return REMOTE_TOKEN; + } + + /// @custom:legacy + /// @notice Legacy getter for the bridge. Use BRIDGE going forward. + function l2Bridge() public view returns (address) { + return BRIDGE; + } + + /// @custom:legacy + /// @notice Legacy getter for REMOTE_TOKEN. + function remoteToken() public view returns (address) { + return REMOTE_TOKEN; + } + + /// @custom:legacy + /// @notice Legacy getter for BRIDGE. + function bridge() public view returns (address) { + return BRIDGE; + } + + /// @dev Returns the number of decimals used to get its user representation. + /// For example, if `decimals` equals `2`, a balance of `505` tokens should + /// be displayed to a user as `5.05` (`505 / 10 ** 2`). + /// NOTE: This information is only used for _display_ purposes: it in + /// no way affects any of the arithmetic of the contract, including + /// {IERC20-balanceOf} and {IERC20-transfer}. + function decimals() public view override returns (uint8) { + return DECIMALS; + } +} diff --git a/src/Optimism/Semver.sol b/src/Optimism/Semver.sol new file mode 100644 index 00000000..4f62fb9c --- /dev/null +++ b/src/Optimism/Semver.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; + +/// @title Semver +/// @notice Semver is a simple contract for managing contract versions. +contract Semver { + /// @notice Contract version number (major). + uint256 private immutable MAJOR_VERSION; + + /// @notice Contract version number (minor). + uint256 private immutable MINOR_VERSION; + + /// @notice Contract version number (patch). + uint256 private immutable PATCH_VERSION; + + /// @param _major Version number (major). + /// @param _minor Version number (minor). + /// @param _patch Version number (patch). + constructor(uint256 _major, uint256 _minor, uint256 _patch) { + MAJOR_VERSION = _major; + MINOR_VERSION = _minor; + PATCH_VERSION = _patch; + } + + /// @notice Returns the full semver contract version. + /// @return Semver contract version as a string. + function version() public view returns (string memory) { + return string(abi.encodePacked(Strings.toString(MAJOR_VERSION), ".", Strings.toString(MINOR_VERSION), ".", Strings.toString(PATCH_VERSION))); + } +} diff --git a/test/DeterministicTokenL2.t.sol b/test/DeterministicTokenL2.t.sol new file mode 100644 index 00000000..0b9cb2ee --- /dev/null +++ b/test/DeterministicTokenL2.t.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import { OptimismMintableERC20 } from "../src/Optimism/OptimismMintableERC20.sol"; + +contract DeterministicTokenL2 is Test { + bytes creationCode = + hex"6101406040523480156200001257600080fd5b506040516200144d3803806200144d833981016040819052620000359162000170565b600160026000858560036200004b8382620002ab565b5060046200005a8282620002ab565b50505060809290925260a05260c0526001600160a01b0393841660e0529390921661010052505060ff166101205262000377565b80516001600160a01b0381168114620000a657600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b600082601f830112620000d357600080fd5b81516001600160401b0380821115620000f057620000f0620000ab565b604051601f8301601f19908116603f011681019082821181831017156200011b576200011b620000ab565b816040528381526020925086838588010111156200013857600080fd5b600091505b838210156200015c57858201830151818301840152908201906200013d565b600093810190920192909252949350505050565b600080600080600060a086880312156200018957600080fd5b62000194866200008e565b9450620001a4602087016200008e565b60408701519094506001600160401b0380821115620001c257600080fd5b620001d089838a01620000c1565b94506060880151915080821115620001e757600080fd5b50620001f688828901620000c1565b925050608086015160ff811681146200020e57600080fd5b809150509295509295909350565b600181811c908216806200023157607f821691505b6020821081036200025257634e487b7160e01b600052602260045260246000fd5b50919050565b601f821115620002a657600081815260208120601f850160051c81016020861015620002815750805b601f850160051c820191505b81811015620002a2578281556001016200028d565b5050505b505050565b81516001600160401b03811115620002c757620002c7620000ab565b620002df81620002d884546200021c565b8462000258565b602080601f831160018114620003175760008415620002fe5750858301515b600019600386901b1c1916600185901b178555620002a2565b600085815260208120601f198616915b82811015620003485788860151825594840194600190910190840162000327565b5085821015620003675787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60805160a05160c05160e051610100516101205161106a620003e360003960006101f70152600081816102bd0152818161031f0152818161049c01526105f001526000818161016901526102e30152600061058c015260006105630152600061053a015261106a6000f3fe608060405234801561001057600080fd5b50600436106101375760003560e01c806370a08231116100b8578063ae1f6aaf1161007c578063ae1f6aaf146102bb578063c01e1bd6146102e1578063d6c0b2c4146102e1578063dd62ed3e14610307578063e78cea92146102bb578063ee9a31a21461031a57600080fd5b806370a082311461025157806395d89b411461027a5780639dc29fac14610282578063a457c2d714610295578063a9059cbb146102a857600080fd5b806323b872dd116100ff57806323b872dd146101dd578063313ce567146101f0578063395093511461022157806340c10f191461023457806354fd4d501461024957600080fd5b806301ffc9a71461013c578063033964be1461016457806306fdde03146101a3578063095ea7b3146101b857806318160ddd146101cb575b600080fd5b61014f61014a366004610dbd565b610341565b60405190151581526020015b60405180910390f35b61018b7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b03909116815260200161015b565b6101ab61039f565b60405161015b9190610e12565b61014f6101c6366004610e61565b610431565b6002545b60405190815260200161015b565b61014f6101eb366004610e8b565b61044b565b60405160ff7f000000000000000000000000000000000000000000000000000000000000000016815260200161015b565b61014f61022f366004610e61565b61046f565b610247610242366004610e61565b610491565b005b6101ab610533565b6101cf61025f366004610ec7565b6001600160a01b031660009081526020819052604090205490565b6101ab6105d6565b610247610290366004610e61565b6105e5565b61014f6102a3366004610e61565b610672565b61014f6102b6366004610e61565b6106ed565b7f000000000000000000000000000000000000000000000000000000000000000061018b565b7f000000000000000000000000000000000000000000000000000000000000000061018b565b6101cf610315366004610ee2565b6106fb565b61018b7f000000000000000000000000000000000000000000000000000000000000000081565b60006301ffc9a760e01b631d1d8b6360e01b63ec4fc8e360e01b6001600160e01b0319851683148061037f57506001600160e01b0319858116908316145b8061039657506001600160e01b0319858116908216145b95945050505050565b6060600380546103ae90610f15565b80601f01602080910402602001604051908101604052809291908181526020018280546103da90610f15565b80156104275780601f106103fc57610100808354040283529160200191610427565b820191906000526020600020905b81548152906001019060200180831161040a57829003601f168201915b5050505050905090565b60003361043f818585610726565b60019150505b92915050565b60003361045985828561084b565b6104648585856108c5565b506001949350505050565b60003361043f81858561048283836106fb565b61048c9190610f4f565b610726565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146104e25760405162461bcd60e51b81526004016104d990610f70565b60405180910390fd5b6104ec8282610a69565b816001600160a01b03167f0f6798a560793a54c3bcfe86a93cde1e73087d944c0ea20544137d41213968858260405161052791815260200190565b60405180910390a25050565b606061055e7f0000000000000000000000000000000000000000000000000000000000000000610b28565b6105877f0000000000000000000000000000000000000000000000000000000000000000610b28565b6105b07f0000000000000000000000000000000000000000000000000000000000000000610b28565b6040516020016105c293929190610fc4565b604051602081830303815290604052905090565b6060600480546103ae90610f15565b336001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161461062d5760405162461bcd60e51b81526004016104d990610f70565b6106378282610bbb565b816001600160a01b03167fcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca58260405161052791815260200190565b6000338161068082866106fb565b9050838110156106e05760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b60648201526084016104d9565b6104648286868403610726565b60003361043f8185856108c5565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6001600160a01b0383166107885760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b60648201526084016104d9565b6001600160a01b0382166107e95760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b60648201526084016104d9565b6001600160a01b0383811660008181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b600061085784846106fb565b905060001981146108bf57818110156108b25760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e636500000060448201526064016104d9565b6108bf8484848403610726565b50505050565b6001600160a01b0383166109295760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b60648201526084016104d9565b6001600160a01b03821661098b5760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b60648201526084016104d9565b6001600160a01b03831660009081526020819052604090205481811015610a035760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b60648201526084016104d9565b6001600160a01b03848116600081815260208181526040808320878703905593871680835291849020805487019055925185815290927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a36108bf565b6001600160a01b038216610abf5760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f20616464726573730060448201526064016104d9565b8060026000828254610ad19190610f4f565b90915550506001600160a01b038216600081815260208181526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b60606000610b3583610ce5565b600101905060008167ffffffffffffffff811115610b5557610b5561101e565b6040519080825280601f01601f191660200182016040528015610b7f576020820181803683370190505b5090508181016020015b600019016f181899199a1a9b1b9c1cb0b131b232b360811b600a86061a8153600a8504945084610b8957509392505050565b6001600160a01b038216610c1b5760405162461bcd60e51b815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736044820152607360f81b60648201526084016104d9565b6001600160a01b03821660009081526020819052604090205481811015610c8f5760405162461bcd60e51b815260206004820152602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e604482015261636560f01b60648201526084016104d9565b6001600160a01b0383166000818152602081815260408083208686039055600280548790039055518581529192917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910161083e565b60008072184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b8310610d245772184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b830492506040015b6d04ee2d6d415b85acef81000000008310610d50576d04ee2d6d415b85acef8100000000830492506020015b662386f26fc100008310610d6e57662386f26fc10000830492506010015b6305f5e1008310610d86576305f5e100830492506008015b6127108310610d9a57612710830492506004015b60648310610dac576064830492506002015b600a83106104455760010192915050565b600060208284031215610dcf57600080fd5b81356001600160e01b031981168114610de757600080fd5b9392505050565b60005b83811015610e09578181015183820152602001610df1565b50506000910152565b6020815260008251806020840152610e31816040850160208701610dee565b601f01601f19169190910160400192915050565b80356001600160a01b0381168114610e5c57600080fd5b919050565b60008060408385031215610e7457600080fd5b610e7d83610e45565b946020939093013593505050565b600080600060608486031215610ea057600080fd5b610ea984610e45565b9250610eb760208501610e45565b9150604084013590509250925092565b600060208284031215610ed957600080fd5b610de782610e45565b60008060408385031215610ef557600080fd5b610efe83610e45565b9150610f0c60208401610e45565b90509250929050565b600181811c90821680610f2957607f821691505b602082108103610f4957634e487b7160e01b600052602260045260246000fd5b50919050565b8082018082111561044557634e487b7160e01b600052601160045260246000fd5b60208082526034908201527f4f7074696d69736d4d696e7461626c6545524332303a206f6e6c79206272696460408201527333b29031b0b71036b4b73a1030b73210313ab93760611b606082015260800190565b60008451610fd6818460208901610dee565b8083019050601760f91b8082528551610ff6816001850160208a01610dee565b60019201918201528351611011816002840160208801610dee565b0160020195945050505050565b634e487b7160e01b600052604160045260246000fdfea2646970667358221220dfbd177392afbdbd8cbdcd64a92c69f67012b5bf0e778570c71cd97c362379c864736f6c63430008120033"; + + function setUp() public { } + + function testInitCode() public { + address tokenFactory = 0x4200000000000000000000000000000000000012; + address bridge = 0x4200000000000000000000000000000000000010; + address remoteToken = 0xDa8Df672aCfa4Ee1be95FBC1c2e0FA97d49F7BDe; + + string memory name = "IP Tokens of IPNFT #42"; + string memory symbol = "CYCL-01"; + + bytes32 salt = keccak256(abi.encode(remoteToken, name, symbol)); + assertEq(creationCode, type(OptimismMintableERC20).creationCode); + + address predictedAddress = address( + uint160( + uint256( + keccak256( + abi.encodePacked( + bytes1(0xff), + tokenFactory, + salt, + keccak256(abi.encodePacked(type(OptimismMintableERC20).creationCode, abi.encode(bridge, remoteToken, name, symbol, 18))) + ) + ) + ) + ) + ); + vm.startPrank(tokenFactory); + OptimismMintableERC20 token = new OptimismMintableERC20{ salt: salt }(bridge, remoteToken, name, symbol, 18); + assertEq(address(token), predictedAddress); + } +}