From c1f796b8aa0e30f2c5c65d935686da8778313ecc Mon Sep 17 00:00:00 2001 From: 0xDiscotech <131301107+0xDiscotech@users.noreply.github.com> Date: Mon, 30 Sep 2024 20:18:19 -0300 Subject: [PATCH 01/10] refactor: rename mint and burn functions on superchain erc20 * chore: rename optimism superchain erc20 to superchain erc20 --- .../contracts-bedrock/scripts/L2Genesis.s.sol | 4 +- .../src/L2/L2StandardBridgeInterop.sol | 16 ++--- .../src/L2/OptimismSuperchainERC20Beacon.sol | 4 +- .../src/L2/OptimismSuperchainERC20Factory.sol | 22 +++--- ...uperchainERC20.sol => SuperchainERC20.sol} | 39 ++++++----- .../src/L2/SuperchainERC20Bridge.sol | 4 +- .../interfaces/IL2StandardBridgeInterop.sol | 6 +- .../interfaces/IMintableAndBurnableERC20.sol | 4 +- .../IOptimismSuperchainERC20Factory.sol | 10 +-- ...perchainERC20.sol => ISuperchainERC20.sol} | 22 +++--- .../src/libraries/Predeploys.sol | 2 +- .../test/L2/L2StandardBridgeInterop.t.sol | 31 ++++++--- .../L2/OptimismSuperchainERC20Factory.t.sol | 10 +-- ...chainERC20.t.sol => SuperchainERC20.t.sol} | 68 +++++++++---------- .../test/L2/SuperchainERC20Bridge.t.sol | 10 +-- .../OptimismSuperchainERC20.t.sol | 8 +-- .../fuzz/Protocol.guided.t.sol | 6 +- .../fuzz/Protocol.unguided.t.sol | 12 ++-- .../handlers/Protocol.t.sol | 18 ++--- .../MockL2ToL2CrossDomainMessenger.t.sol | 6 +- ...imismSuperchainERC20ForToBProperties.t.sol | 4 +- .../test/vendor/Initializable.t.sol | 2 +- .../test/vendor/InitializableOZv5.t.sol | 8 +-- 23 files changed, 162 insertions(+), 154 deletions(-) rename packages/contracts-bedrock/src/L2/{OptimismSuperchainERC20.sol => SuperchainERC20.sol} (73%) rename packages/contracts-bedrock/src/L2/interfaces/{IOptimismSuperchainERC20.sol => ISuperchainERC20.sol} (67%) rename packages/contracts-bedrock/test/L2/{OptimismSuperchainERC20.t.sol => SuperchainERC20.t.sol} (80%) diff --git a/packages/contracts-bedrock/scripts/L2Genesis.s.sol b/packages/contracts-bedrock/scripts/L2Genesis.s.sol index f93ab0e3b718..ebc0c4a2dba9 100644 --- a/packages/contracts-bedrock/scripts/L2Genesis.s.sol +++ b/packages/contracts-bedrock/scripts/L2Genesis.s.sol @@ -542,8 +542,8 @@ contract L2Genesis is Deployer { /// @notice This predeploy is following the safety invariant #2. function setOptimismSuperchainERC20Beacon() internal { address superchainERC20Impl = Predeploys.OPTIMISM_SUPERCHAIN_ERC20; - console.log("Setting %s implementation at: %s", "OptimismSuperchainERC20", superchainERC20Impl); - vm.etch(superchainERC20Impl, vm.getDeployedCode("OptimismSuperchainERC20.sol:OptimismSuperchainERC20")); + console.log("Setting %s implementation at: %s", "SuperchainERC20", superchainERC20Impl); + vm.etch(superchainERC20Impl, vm.getDeployedCode("SuperchainERC20.sol:SuperchainERC20")); OptimismSuperchainERC20Beacon beacon = new OptimismSuperchainERC20Beacon(superchainERC20Impl); address beaconImpl = Predeploys.predeployToCodeNamespace(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON); diff --git a/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol b/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol index eb25a406ede7..0061cfd11535 100644 --- a/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol +++ b/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol @@ -19,7 +19,7 @@ error InvalidDecimals(); /// @notice Thrown when the legacy address is not found in the OptimismMintableERC20Factory. error InvalidLegacyERC20Address(); -/// @notice Thrown when the OptimismSuperchainERC20 address is not found in the OptimismSuperchainERC20Factory. +/// @notice Thrown when the SuperchainERC20 address is not found in the OptimismSuperchainERC20Factory. error InvalidSuperchainERC20Address(); /// @notice Thrown when the remote addresses of the tokens are not the same. @@ -30,7 +30,7 @@ error InvalidTokenPair(); /// @title L2StandardBridgeInterop /// @notice The L2StandardBridgeInterop is an extension of the L2StandardBridge that allows for /// the conversion of tokens between legacy tokens (OptimismMintableERC20 or StandardL2ERC20) -/// and OptimismSuperchainERC20 tokens. +/// and SuperchainERC20 tokens. contract L2StandardBridgeInterop is L2StandardBridge { /// @notice Emitted when a conversion is made. /// @param from The token being converted from. @@ -40,9 +40,9 @@ contract L2StandardBridgeInterop is L2StandardBridge { event Converted(address indexed from, address indexed to, address indexed caller, uint256 amount); /// @notice Semantic version. - /// @custom:semver +interop-beta.1 + /// @custom:semver +interop-beta.2 function version() public pure override returns (string memory) { - return string.concat(super.version(), "+interop-beta.1"); + return string.concat(super.version(), "+interop-beta.2"); } /// @notice Converts `amount` of `from` token to `to` token. @@ -52,8 +52,8 @@ contract L2StandardBridgeInterop is L2StandardBridge { function convert(address _from, address _to, uint256 _amount) external { _validatePair(_from, _to); - IMintableAndBurnableERC20(_from).burn(msg.sender, _amount); - IMintableAndBurnableERC20(_to).mint(msg.sender, _amount); + IMintableAndBurnableERC20(_from).__superchainMint(msg.sender, _amount); + IMintableAndBurnableERC20(_to).__superchainBurn(msg.sender, _amount); emit Converted(_from, _to, msg.sender, _amount); } @@ -75,14 +75,14 @@ contract L2StandardBridgeInterop is L2StandardBridge { /// @notice Validates that the tokens are deployed by the correct factory. /// @param _legacyAddr The legacy token address (OptimismMintableERC20 or StandardL2ERC20). - /// @param _superAddr The OptimismSuperchainERC20 address. + /// @param _superAddr The SuperchainERC20 address. function _validateFactories(address _legacyAddr, address _superAddr) internal view { // 2. Valid legacy check address _legacyRemoteToken = IOptimismERC20Factory(Predeploys.OPTIMISM_MINTABLE_ERC20_FACTORY).deployments(_legacyAddr); if (_legacyRemoteToken == address(0)) revert InvalidLegacyERC20Address(); - // 3. Valid OptimismSuperchainERC20 check + // 3. Valid SuperchainERC20 check address _superRemoteToken = IOptimismERC20Factory(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY).deployments(_superAddr); if (_superRemoteToken == address(0)) revert InvalidSuperchainERC20Address(); diff --git a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Beacon.sol b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Beacon.sol index d1160819a2ec..6935793a4765 100644 --- a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Beacon.sol +++ b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Beacon.sol @@ -7,9 +7,9 @@ import { ISemver } from "src/universal/interfaces/ISemver.sol"; /// @custom:proxied /// @custom:predeployed 0x4200000000000000000000000000000000000027 /// @title OptimismSuperchainERC20Beacon -/// @notice OptimismSuperchainERC20Beacon is the beacon proxy for the OptimismSuperchainERC20 implementation. +/// @notice OptimismSuperchainERC20Beacon is the beacon proxy for the SuperchainERC20 implementation. contract OptimismSuperchainERC20Beacon is IBeacon, ISemver { - /// @notice Address of the OptimismSuperchainERC20 implementation. + /// @notice Address of the SuperchainERC20 implementation. address internal immutable IMPLEMENTATION; /// @notice Semantic version. diff --git a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol index 8e61dca87b76..4c9b43c45af9 100644 --- a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol +++ b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.25; import { IOptimismSuperchainERC20Factory } from "src/L2/interfaces/IOptimismSuperchainERC20Factory.sol"; import { ISemver } from "src/universal/interfaces/ISemver.sol"; -import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; +import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; import { BeaconProxy } from "@openzeppelin/contracts-v5/proxy/beacon/BeaconProxy.sol"; import { CREATE3 } from "@rari-capital/solmate/src/utils/CREATE3.sol"; @@ -11,15 +11,15 @@ import { CREATE3 } from "@rari-capital/solmate/src/utils/CREATE3.sol"; /// @custom:proxied /// @custom:predeployed 0x4200000000000000000000000000000000000026 /// @title OptimismSuperchainERC20Factory -/// @notice OptimismSuperchainERC20Factory is a factory contract that deploys OptimismSuperchainERC20 Beacon Proxies +/// @notice OptimismSuperchainERC20Factory is a factory contract that deploys SuperchainERC20 Beacon Proxies /// using CREATE3. contract OptimismSuperchainERC20Factory is IOptimismSuperchainERC20Factory, ISemver { - /// @notice Mapping of the deployed OptimismSuperchainERC20 to the remote token address. + /// @notice Mapping of the deployed SuperchainERC20 to the remote token address. /// This is used to keep track of the token deployments. mapping(address superchainToken => address remoteToken) public deployments; - /// @notice Emitted when an OptimismSuperchainERC20 is deployed. - /// @param superchainToken Address of the OptimismSuperchainERC20 deployment. + /// @notice Emitted when an SuperchainERC20 is deployed. + /// @param superchainToken Address of the SuperchainERC20 deployment. /// @param remoteToken Address of the corresponding token on the remote chain. /// @param deployer Address of the account that deployed the token. event OptimismSuperchainERC20Created( @@ -30,12 +30,12 @@ contract OptimismSuperchainERC20Factory is IOptimismSuperchainERC20Factory, ISem /// @custom:semver 1.0.0-beta.3 string public constant version = "1.0.0-beta.3"; - /// @notice Deploys a OptimismSuperchainERC20 Beacon Proxy using CREATE3. + /// @notice Deploys a SuperchainERC20 Beacon Proxy using CREATE3. /// @param _remoteToken Address of the remote token. - /// @param _name Name of the OptimismSuperchainERC20. - /// @param _symbol Symbol of the OptimismSuperchainERC20. - /// @param _decimals Decimals of the OptimismSuperchainERC20. - /// @return _superchainERC20 Address of the OptimismSuperchainERC20 deployment. + /// @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, @@ -46,7 +46,7 @@ contract OptimismSuperchainERC20Factory is IOptimismSuperchainERC20Factory, ISem returns (address _superchainERC20) { bytes memory initCallData = - abi.encodeCall(OptimismSuperchainERC20.initialize, (_remoteToken, _name, _symbol, _decimals)); + abi.encodeCall(SuperchainERC20.initialize, (_remoteToken, _name, _symbol, _decimals)); bytes memory creationCode = bytes.concat( type(BeaconProxy).creationCode, abi.encode(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON, initCallData) diff --git a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol similarity index 73% rename from packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol rename to packages/contracts-bedrock/src/L2/SuperchainERC20.sol index db80ac39d045..2f5e48912b89 100644 --- a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; -import { IOptimismSuperchainERC20Extension } from "src/L2/interfaces/IOptimismSuperchainERC20.sol"; +import { ISuperchainERC20Extension } from "src/L2/interfaces/ISuperchainERC20.sol"; import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; import { ISemver } from "src/universal/interfaces/ISemver.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; @@ -10,22 +10,22 @@ import { ERC20 } from "@solady/tokens/ERC20.sol"; import { Initializable } from "@openzeppelin/contracts-v5/proxy/utils/Initializable.sol"; /// @custom:proxied true -/// @title OptimismSuperchainERC20 -/// @notice OptimismSuperchainERC20 is a standard extension of the base ERC20 token contract that unifies ERC20 token +/// @title SuperchainERC20 +/// @notice SuperchainERC20 is a standard extension of the base ERC20 token contract that unifies ERC20 token /// bridging to make it fungible across the Superchain. This construction allows the L2StandardBridge to burn /// and mint tokens. This makes it possible to convert a valid OptimismMintableERC20 token to a -/// OptimismSuperchainERC20 token, turning it fungible and interoperable across the superchain. Likewise, it +/// SuperchainERC20 token, turning it fungible and interoperable across the superchain. Likewise, it /// also enables the inverse conversion path. /// Moreover, it builds on top of the L2ToL2CrossDomainMessenger for both replay protection and domain binding. -contract OptimismSuperchainERC20 is ERC20, Initializable, ERC165, IOptimismSuperchainERC20Extension, ISemver { - /// @notice Storage slot that the OptimismSuperchainERC20Metadata struct is stored at. - /// keccak256(abi.encode(uint256(keccak256("optimismSuperchainERC20.metadata")) - 1)) & ~bytes32(uint256(0xff)); +contract SuperchainERC20 is ERC20, Initializable, ERC165, ISuperchainERC20Extension, ISemver { + /// @notice Storage slot that the SuperchainERC20Metadata struct is stored at. + /// keccak256(abi.encode(uint256(keccak256("SuperchainERC20.metadata")) - 1)) & ~bytes32(uint256(0xff)); bytes32 internal constant OPTIMISM_SUPERCHAIN_ERC20_METADATA_SLOT = 0x07f04e84143df95a6373fcf376312ae41da81a193a3089073a54f47a74d8fb00; - /// @notice Storage struct for the OptimismSuperchainERC20 metadata. - /// @custom:storage-location erc7201:optimismSuperchainERC20.metadata - struct OptimismSuperchainERC20Metadata { + /// @notice Storage struct for the SuperchainERC20 metadata. + /// @custom:storage-location erc7201:SuperchainERC20.metadata + struct SuperchainERC20Metadata { /// @notice Address of the corresponding version of this token on the remote chain. address remoteToken; /// @notice Name of the token @@ -36,8 +36,8 @@ contract OptimismSuperchainERC20 is ERC20, Initializable, ERC165, IOptimismSuper uint8 decimals; } - /// @notice Returns the storage for the OptimismSuperchainERC20Metadata. - function _getStorage() private pure returns (OptimismSuperchainERC20Metadata storage _storage) { + /// @notice Returns the storage for the SuperchainERC20Metadata. + function _getStorage() private pure returns (SuperchainERC20Metadata storage _storage) { assembly { _storage.slot := OPTIMISM_SUPERCHAIN_ERC20_METADATA_SLOT } @@ -52,10 +52,10 @@ contract OptimismSuperchainERC20 is ERC20, Initializable, ERC165, IOptimismSuper } /// @notice Semantic version. - /// @custom:semver 1.0.0-beta.5 - string public constant version = "1.0.0-beta.5"; + /// @custom:semver 1.0.0-beta.6 + string public constant version = "1.0.0-beta.6"; - /// @notice Constructs the OptimismSuperchainERC20 contract. + /// @notice Constructs the SuperchainERC20 contract. constructor() { _disableInitializers(); } @@ -74,7 +74,7 @@ contract OptimismSuperchainERC20 is ERC20, Initializable, ERC165, IOptimismSuper external initializer { - OptimismSuperchainERC20Metadata storage _storage = _getStorage(); + SuperchainERC20Metadata storage _storage = _getStorage(); _storage.remoteToken = _remoteToken; _storage.name = _name; _storage.symbol = _symbol; @@ -84,7 +84,7 @@ contract OptimismSuperchainERC20 is ERC20, Initializable, ERC165, IOptimismSuper /// @notice Allows the L2StandardBridge and SuperchainERC20Bridge 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 onlyAuthorizedBridge { + function __superchainMint(address _to, uint256 _amount) external virtual onlyAuthorizedBridge { if (_to == address(0)) revert ZeroAddress(); _mint(_to, _amount); @@ -95,7 +95,7 @@ contract OptimismSuperchainERC20 is ERC20, Initializable, ERC165, IOptimismSuper /// @notice Allows the L2StandardBridge and SuperchainERC20Bridge 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 onlyAuthorizedBridge { + function __superchainBurn(address _from, uint256 _amount) external virtual onlyAuthorizedBridge { if (_from == address(0)) revert ZeroAddress(); _burn(_from, _amount); @@ -132,7 +132,6 @@ contract OptimismSuperchainERC20 is ERC20, Initializable, ERC165, IOptimismSuper /// @param _interfaceId Interface ID to check. /// @return Whether or not the interface is supported by this contract. function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) { - return - _interfaceId == type(IOptimismSuperchainERC20Extension).interfaceId || super.supportsInterface(_interfaceId); + return _interfaceId == type(ISuperchainERC20Extension).interfaceId || super.supportsInterface(_interfaceId); } } diff --git a/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol b/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol index 8ab6fd8382c8..015985a787f8 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol @@ -29,7 +29,7 @@ contract SuperchainERC20Bridge is ISuperchainERC20Bridge { /// @param _amount Amount of tokens to send. /// @param _chainId Chain ID of the destination chain. function sendERC20(address _token, address _to, uint256 _amount, uint256 _chainId) external { - IMintableAndBurnableERC20(_token).burn(msg.sender, _amount); + IMintableAndBurnableERC20(_token).__superchainBurn(msg.sender, _amount); bytes memory message = abi.encodeCall(this.relayERC20, (_token, msg.sender, _to, _amount)); IL2ToL2CrossDomainMessenger(MESSENGER).sendMessage(_chainId, address(this), message); @@ -51,7 +51,7 @@ contract SuperchainERC20Bridge is ISuperchainERC20Bridge { uint256 source = IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSource(); - IMintableAndBurnableERC20(_token).mint(_to, _amount); + IMintableAndBurnableERC20(_token).__superchainMint(_to, _amount); emit RelayERC20(_token, _from, _to, _amount, source); } diff --git a/packages/contracts-bedrock/src/L2/interfaces/IL2StandardBridgeInterop.sol b/packages/contracts-bedrock/src/L2/interfaces/IL2StandardBridgeInterop.sol index 227b48881fa6..6207025b0c3d 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/IL2StandardBridgeInterop.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/IL2StandardBridgeInterop.sol @@ -4,11 +4,7 @@ pragma solidity ^0.8.0; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IStandardBridge } from "src/universal/interfaces/IStandardBridge.sol"; import { ICrossDomainMessenger } from "src/universal/interfaces/ICrossDomainMessenger.sol"; - -interface IMintableAndBurnable is IERC20 { - function mint(address, uint256) external; - function burn(address, uint256) external; -} +import { IMintableAndBurnableERC20 } from "src/L2/interfaces/IMintableAndBurnableERC20.sol"; interface IL2StandardBridgeInterop is IStandardBridge { error InvalidDecimals(); diff --git a/packages/contracts-bedrock/src/L2/interfaces/IMintableAndBurnableERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/IMintableAndBurnableERC20.sol index 166fa0c8077f..d6070df06d04 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/IMintableAndBurnableERC20.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/IMintableAndBurnableERC20.sol @@ -9,10 +9,10 @@ interface IMintableAndBurnableERC20 is IERC20 { /// @notice Mints `_amount` of tokens to `_to`. /// @param _to Address to mint tokens to. /// @param _amount Amount of tokens to mint. - function mint(address _to, uint256 _amount) external; + function __superchainMint(address _to, uint256 _amount) external; /// @notice Burns `_amount` of tokens from `_from`. /// @param _from Address to burn tokens from. /// @param _amount Amount of tokens to burn. - function burn(address _from, uint256 _amount) external; + function __superchainBurn(address _from, uint256 _amount) external; } diff --git a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20Factory.sol b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20Factory.sol index aa23405fa527..9934a6dee877 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20Factory.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20Factory.sol @@ -6,12 +6,12 @@ import { IOptimismERC20Factory } from "./IOptimismERC20Factory.sol"; /// @title IOptimismSuperchainERC20Factory /// @notice Interface for OptimismSuperchainERC20Factory. interface IOptimismSuperchainERC20Factory is IOptimismERC20Factory { - /// @notice Deploys a OptimismSuperchainERC20 Beacon Proxy using CREATE3. + /// @notice Deploys a SuperchainERC20 Beacon Proxy using CREATE3. /// @param _remoteToken Address of the remote token. - /// @param _name Name of the OptimismSuperchainERC20. - /// @param _symbol Symbol of the OptimismSuperchainERC20. - /// @param _decimals Decimals of the OptimismSuperchainERC20. - /// @return _superchainERC20 Address of the OptimismSuperchainERC20 deployment. + /// @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, diff --git a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol similarity index 67% rename from packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol rename to packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol index 61a286234fcc..2035b37f9397 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol @@ -4,9 +4,9 @@ pragma solidity ^0.8.0; // Interfaces import { IERC20Solady } from "src/vendor/interfaces/IERC20Solady.sol"; -/// @title IOptimismSuperchainERC20Errors -/// @notice Interface containing the errors added in the OptimismSuperchainERC20 implementation. -interface IOptimismSuperchainERC20Errors { +/// @title ISuperchainERC20Errors +/// @notice Interface containing the errors added in the SuperchainERC20 implementation. +interface ISuperchainERC20Errors { /// @notice Thrown when attempting to perform an operation and the account is the zero address. error ZeroAddress(); @@ -15,9 +15,9 @@ interface IOptimismSuperchainERC20Errors { error OnlyAuthorizedBridge(); } -/// @title IOptimismSuperchainERC20Extension -/// @notice This interface is available on the OptimismSuperchainERC20 contract. -interface IOptimismSuperchainERC20Extension is IOptimismSuperchainERC20Errors { +/// @title ISuperchainERC20Extension +/// @notice This interface is available on the SuperchainERC20 contract. +interface ISuperchainERC20Extension is ISuperchainERC20Errors { /// @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. @@ -31,17 +31,17 @@ interface IOptimismSuperchainERC20Extension is IOptimismSuperchainERC20Errors { /// @notice Allows the L2StandardBridge and SuperchainERC20Bridge to mint tokens. /// @param _to Address to mint tokens to. /// @param _amount Amount of tokens to mint. - function mint(address _to, uint256 _amount) external; + function __superchainMint(address _to, uint256 _amount) external; /// @notice Allows the L2StandardBridge and SuperchainERC20Bridge to burn tokens. /// @param _from Address to burn tokens from. /// @param _amount Amount of tokens to burn. - function burn(address _from, uint256 _amount) external; + function __superchainBurn(address _from, uint256 _amount) external; /// @notice Returns the address of the corresponding version of this token on the remote chain. function remoteToken() external view returns (address); } -/// @title IOptimismSuperchainERC20 -/// @notice Combines Solady's ERC20 interface with the OptimismSuperchainERC20Extension interface. -interface IOptimismSuperchainERC20 is IERC20Solady, IOptimismSuperchainERC20Extension { } +/// @title ISuperchainERC20 +/// @notice Combines Solady's ERC20 interface with the SuperchainERC20Extension interface. +interface ISuperchainERC20 is IERC20Solady, ISuperchainERC20Extension { } diff --git a/packages/contracts-bedrock/src/libraries/Predeploys.sol b/packages/contracts-bedrock/src/libraries/Predeploys.sol index 5bc1d75a1f06..891ab78c648f 100644 --- a/packages/contracts-bedrock/src/libraries/Predeploys.sol +++ b/packages/contracts-bedrock/src/libraries/Predeploys.sol @@ -102,7 +102,7 @@ library Predeploys { address internal constant OPTIMISM_SUPERCHAIN_ERC20_BEACON = 0x4200000000000000000000000000000000000027; // TODO: Precalculate the address of the implementation contract - /// @notice Arbitrary address of the OptimismSuperchainERC20 implementation contract. + /// @notice Arbitrary address of the SuperchainERC20 implementation contract. address internal constant OPTIMISM_SUPERCHAIN_ERC20 = 0xB9415c6cA93bdC545D4c5177512FCC22EFa38F28; /// @notice Address of the SuperchainERC20Bridge predeploy. diff --git a/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol b/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol index 1d11ca1d510a..09d4ecefb5b6 100644 --- a/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol +++ b/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol @@ -9,7 +9,8 @@ import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; // Interfaces -import { IL2StandardBridgeInterop, IMintableAndBurnable } from "src/L2/interfaces/IL2StandardBridgeInterop.sol"; +import { IMintableAndBurnableERC20 } from "src/L2/interfaces/IMintableAndBurnableERC20.sol"; +import { IL2StandardBridgeInterop } from "src/L2/interfaces/IL2StandardBridgeInterop.sol"; import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import { IOptimismMintableERC20 } from "src/universal/interfaces/IOptimismMintableERC20.sol"; @@ -57,9 +58,9 @@ contract L2StandardBridgeInterop_Test is Bridge_Initializer { } } -/// @notice Test suite when converting from a legacy token to a OptimismSuperchainERC20 token +/// @notice Test suite when converting from a legacy token to a SuperchainERC20 token contract L2StandardBridgeInterop_LegacyToSuper_Test is L2StandardBridgeInterop_Test { - /// @notice Set up the test for converting from a legacy token to a OptimismSuperchainERC20 token + /// @notice Set up the test for converting from a legacy token to a SuperchainERC20 token function _setUpLegacyToSuper(address _from, address _to) internal { // Assume _assumeAddress(_from); @@ -202,9 +203,15 @@ contract L2StandardBridgeInterop_LegacyToSuper_Test is L2StandardBridgeInterop_T // Mock and expect the `burn` and `mint` functions _mockAndExpect( - _from, abi.encodeWithSelector(IMintableAndBurnable.burn.selector, _caller, _amount), abi.encode() + _from, + abi.encodeWithSelector(IMintableAndBurnableERC20.__superchainBurn.selector, _caller, _amount), + abi.encode() + ); + _mockAndExpect( + _to, + abi.encodeWithSelector(IMintableAndBurnableERC20.__superchainMint.selector, _caller, _amount), + abi.encode() ); - _mockAndExpect(_to, abi.encodeWithSelector(IMintableAndBurnable.mint.selector, _caller, _amount), abi.encode()); // Act vm.prank(_caller); @@ -212,9 +219,9 @@ contract L2StandardBridgeInterop_LegacyToSuper_Test is L2StandardBridgeInterop_T } } -/// @notice Test suite when converting from a OptimismSuperchainERC20 token to a legacy token +/// @notice Test suite when converting from a SuperchainERC20 token to a legacy token contract L2StandardBridgeInterop_SuperToLegacy_Test is L2StandardBridgeInterop_Test { - /// @notice Set up the test for converting from a OptimismSuperchainERC20 token to a legacy token + /// @notice Set up the test for converting from a SuperchainERC20 token to a legacy token function _setUpSuperToLegacy(address _from, address _to) internal { // Assume _assumeAddress(_from); @@ -358,9 +365,15 @@ contract L2StandardBridgeInterop_SuperToLegacy_Test is L2StandardBridgeInterop_T // Mock and expect the `burn` and `mint` functions _mockAndExpect( - _from, abi.encodeWithSelector(IMintableAndBurnable.burn.selector, _caller, _amount), abi.encode() + _from, + abi.encodeWithSelector(IMintableAndBurnableERC20.__superchainBurn.selector, _caller, _amount), + abi.encode() + ); + _mockAndExpect( + _to, + abi.encodeWithSelector(IMintableAndBurnableERC20.__superchainMint.selector, _caller, _amount), + abi.encode() ); - _mockAndExpect(_to, abi.encodeWithSelector(IMintableAndBurnable.mint.selector, _caller, _amount), abi.encode()); // Act vm.prank(_caller); diff --git a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Factory.t.sol b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Factory.t.sol index 3636317156a7..c67507639eee 100644 --- a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Factory.t.sol +++ b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Factory.t.sol @@ -9,7 +9,7 @@ import { Predeploys } from "src/libraries/Predeploys.sol"; import { CREATE3, Bytes32AddressLib } from "@rari-capital/solmate/src/utils/CREATE3.sol"; // Target contract -import { IOptimismSuperchainERC20 } from "src/L2/interfaces/IOptimismSuperchainERC20.sol"; +import { ISuperchainERC20 } from "src/L2/interfaces/ISuperchainERC20.sol"; import { IOptimismSuperchainERC20Factory } from "src/L2/interfaces/IOptimismSuperchainERC20Factory.sol"; /// @title OptimismSuperchainERC20FactoryTest @@ -50,10 +50,10 @@ contract OptimismSuperchainERC20FactoryTest is Bridge_Initializer { // Assert assertTrue(addr == deployment); - assertTrue(IOptimismSuperchainERC20(deployment).decimals() == _decimals); - assertTrue(IOptimismSuperchainERC20(deployment).remoteToken() == _remoteToken); - assertEq(IOptimismSuperchainERC20(deployment).name(), _name); - assertEq(IOptimismSuperchainERC20(deployment).symbol(), _symbol); + assertTrue(ISuperchainERC20(deployment).decimals() == _decimals); + assertTrue(ISuperchainERC20(deployment).remoteToken() == _remoteToken); + assertEq(ISuperchainERC20(deployment).name(), _name); + assertEq(ISuperchainERC20(deployment).symbol(), _symbol); assertEq(l2OptimismSuperchainERC20Factory.deployments(deployment), _remoteToken); } diff --git a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol similarity index 80% rename from packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol rename to packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol index 1931328bce4c..5a4242b6dd54 100644 --- a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol @@ -16,27 +16,27 @@ import { IBeacon } from "@openzeppelin/contracts-v5/proxy/beacon/IBeacon.sol"; import { BeaconProxy } from "@openzeppelin/contracts-v5/proxy/beacon/BeaconProxy.sol"; // Target contract -import { OptimismSuperchainERC20, IOptimismSuperchainERC20Extension } from "src/L2/OptimismSuperchainERC20.sol"; -import { IOptimismSuperchainERC20Errors } from "src/L2/interfaces/IOptimismSuperchainERC20.sol"; +import { SuperchainERC20, ISuperchainERC20Extension } from "src/L2/SuperchainERC20.sol"; +import { ISuperchainERC20Errors } from "src/L2/interfaces/ISuperchainERC20.sol"; -/// @title OptimismSuperchainERC20Test -/// @notice Contract for testing the OptimismSuperchainERC20 contract. -contract OptimismSuperchainERC20Test is Test { +/// @title SuperchainERC20Test +/// @notice Contract for testing the SuperchainERC20 contract. +contract SuperchainERC20Test is Test { address internal constant ZERO_ADDRESS = address(0); address internal constant REMOTE_TOKEN = address(0x123); - string internal constant NAME = "OptimismSuperchainERC20"; + string internal constant NAME = "SuperchainERC20"; string internal constant SYMBOL = "SCE"; uint8 internal constant DECIMALS = 18; address internal constant L2_BRIDGE = Predeploys.L2_STANDARD_BRIDGE; address internal constant SUPERCHAIN_ERC20_BRIDGE = Predeploys.SUPERCHAIN_ERC20_BRIDGE; address internal constant MESSENGER = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER; - OptimismSuperchainERC20 public superchainERC20Impl; - OptimismSuperchainERC20 public superchainERC20; + SuperchainERC20 public superchainERC20Impl; + SuperchainERC20 public superchainERC20; /// @notice Sets up the test suite. function setUp() public { - superchainERC20Impl = new OptimismSuperchainERC20(); + superchainERC20Impl = new SuperchainERC20(); // Deploy the OptimismSuperchainERC20Beacon contract _deployBeacon(); @@ -63,7 +63,7 @@ contract OptimismSuperchainERC20Test is Test { ); } - /// @notice Helper function to deploy a proxy of the OptimismSuperchainERC20 contract. + /// @notice Helper function to deploy a proxy of the SuperchainERC20 contract. function _deploySuperchainERC20Proxy( address _remoteToken, string memory _name, @@ -71,13 +71,13 @@ contract OptimismSuperchainERC20Test is Test { uint8 _decimals ) internal - returns (OptimismSuperchainERC20) + returns (SuperchainERC20) { - return OptimismSuperchainERC20( + return SuperchainERC20( address( new BeaconProxy( Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON, - abi.encodeCall(OptimismSuperchainERC20.initialize, (_remoteToken, _name, _symbol, _decimals)) + abi.encodeCall(SuperchainERC20.initialize, (_remoteToken, _name, _symbol, _decimals)) ) ) ); @@ -126,22 +126,22 @@ contract OptimismSuperchainERC20Test is Test { vm.assume(_caller != SUPERCHAIN_ERC20_BRIDGE); // Expect the revert with `OnlyAuthorizedBridge` selector - vm.expectRevert(IOptimismSuperchainERC20Errors.OnlyAuthorizedBridge.selector); + vm.expectRevert(ISuperchainERC20Errors.OnlyAuthorizedBridge.selector); // Call the `mint` function with the non-bridge caller vm.prank(_caller); - superchainERC20.mint(_to, _amount); + superchainERC20.__superchainMint(_to, _amount); } /// @notice Tests the `mint` function reverts when the amount is zero. function testFuzz_mint_zeroAddressTo_reverts(uint256 _amount, bool _returnL2StandardBridge) public { // Expect the revert with `ZeroAddress` selector - vm.expectRevert(IOptimismSuperchainERC20Errors.ZeroAddress.selector); + vm.expectRevert(ISuperchainERC20Errors.ZeroAddress.selector); // Call the `mint` function with the zero address address bridge = _getBridge(_returnL2StandardBridge); vm.prank(bridge); - superchainERC20.mint({ _to: ZERO_ADDRESS, _amount: _amount }); + superchainERC20.__superchainMint({ _to: ZERO_ADDRESS, _amount: _amount }); } /// @notice Tests the `mint` succeeds and emits the `Mint` event. @@ -159,12 +159,12 @@ contract OptimismSuperchainERC20Test is Test { // Look for the emit of the `Mint` event vm.expectEmit(address(superchainERC20)); - emit IOptimismSuperchainERC20Extension.Mint(_to, _amount); + emit ISuperchainERC20Extension.Mint(_to, _amount); // Call the `mint` function with the bridge caller address bridge = _getBridge(_returnL2StandardBridge); vm.prank(bridge); - superchainERC20.mint(_to, _amount); + superchainERC20.__superchainMint(_to, _amount); // Check the total supply and balance of `_to` after the mint were updated correctly assertEq(superchainERC20.totalSupply(), _totalSupplyBefore + _amount); @@ -178,22 +178,22 @@ contract OptimismSuperchainERC20Test is Test { vm.assume(_caller != SUPERCHAIN_ERC20_BRIDGE); // Expect the revert with `OnlyAuthorizedBridge` selector - vm.expectRevert(IOptimismSuperchainERC20Errors.OnlyAuthorizedBridge.selector); + vm.expectRevert(ISuperchainERC20Errors.OnlyAuthorizedBridge.selector); // Call the `burn` function with the non-bridge caller vm.prank(_caller); - superchainERC20.burn(_from, _amount); + superchainERC20.__superchainBurn(_from, _amount); } /// @notice Tests the `burn` function reverts when the amount is zero. function testFuzz_burn_zeroAddressFrom_reverts(uint256 _amount, bool _returnL2StandardBridge) public { // Expect the revert with `ZeroAddress` selector - vm.expectRevert(IOptimismSuperchainERC20Errors.ZeroAddress.selector); + vm.expectRevert(ISuperchainERC20Errors.ZeroAddress.selector); // Call the `burn` function with the zero address address bridge = _getBridge(_returnL2StandardBridge); vm.prank(bridge); - superchainERC20.burn({ _from: ZERO_ADDRESS, _amount: _amount }); + superchainERC20.__superchainBurn({ _from: ZERO_ADDRESS, _amount: _amount }); } /// @notice Tests the `burn` burns the amount and emits the `Burn` event. @@ -204,7 +204,7 @@ contract OptimismSuperchainERC20Test is Test { // Mint some tokens to `_from` so then they can be burned address bridge = _getBridge(_returnL2StandardBridge); vm.prank(bridge); - superchainERC20.mint(_from, _amount); + superchainERC20.__superchainMint(_from, _amount); // Get the total supply and balance of `_from` before the burn to compare later on the assertions uint256 _totalSupplyBefore = superchainERC20.totalSupply(); @@ -216,12 +216,12 @@ contract OptimismSuperchainERC20Test is Test { // Look for the emit of the `Burn` event vm.expectEmit(address(superchainERC20)); - emit IOptimismSuperchainERC20Extension.Burn(_from, _amount); + emit ISuperchainERC20Extension.Burn(_from, _amount); // Call the `burn` function with the bridge caller bridge = _getBridge(_returnL2StandardBridge); vm.prank(bridge); - superchainERC20.burn(_from, _amount); + superchainERC20.__superchainBurn(_from, _amount); // Check the total supply and balance of `_from` after the burn were updated correctly assertEq(superchainERC20.totalSupply(), _totalSupplyBefore - _amount); @@ -230,39 +230,39 @@ contract OptimismSuperchainERC20Test is Test { /// @notice Tests the `decimals` function always returns the correct value. function testFuzz_decimals_succeeds(uint8 _decimals) public { - OptimismSuperchainERC20 _newSuperchainERC20 = _deploySuperchainERC20Proxy(REMOTE_TOKEN, NAME, SYMBOL, _decimals); + SuperchainERC20 _newSuperchainERC20 = _deploySuperchainERC20Proxy(REMOTE_TOKEN, NAME, SYMBOL, _decimals); assertEq(_newSuperchainERC20.decimals(), _decimals); } /// @notice Tests the `REMOTE_TOKEN` function always returns the correct value. function testFuzz_remoteToken_succeeds(address _remoteToken) public { - OptimismSuperchainERC20 _newSuperchainERC20 = _deploySuperchainERC20Proxy(_remoteToken, NAME, SYMBOL, DECIMALS); + SuperchainERC20 _newSuperchainERC20 = _deploySuperchainERC20Proxy(_remoteToken, NAME, SYMBOL, DECIMALS); assertEq(_newSuperchainERC20.remoteToken(), _remoteToken); } /// @notice Tests the `name` function always returns the correct value. function testFuzz_name_succeeds(string memory _name) public { - OptimismSuperchainERC20 _newSuperchainERC20 = _deploySuperchainERC20Proxy(REMOTE_TOKEN, _name, SYMBOL, DECIMALS); + SuperchainERC20 _newSuperchainERC20 = _deploySuperchainERC20Proxy(REMOTE_TOKEN, _name, SYMBOL, DECIMALS); assertEq(_newSuperchainERC20.name(), _name); } /// @notice Tests the `symbol` function always returns the correct value. function testFuzz_symbol_succeeds(string memory _symbol) public { - OptimismSuperchainERC20 _newSuperchainERC20 = _deploySuperchainERC20Proxy(REMOTE_TOKEN, NAME, _symbol, DECIMALS); + SuperchainERC20 _newSuperchainERC20 = _deploySuperchainERC20Proxy(REMOTE_TOKEN, NAME, _symbol, DECIMALS); assertEq(_newSuperchainERC20.symbol(), _symbol); } - /// @notice Tests that the `supportsInterface` function returns true for the `IOptimismSuperchainERC20` interface. + /// @notice Tests that the `supportsInterface` function returns true for the `ISuperchainERC20` interface. function test_supportInterface_succeeds() public view { assertTrue(superchainERC20.supportsInterface(type(IERC165).interfaceId)); - assertTrue(superchainERC20.supportsInterface(type(IOptimismSuperchainERC20Extension).interfaceId)); + assertTrue(superchainERC20.supportsInterface(type(ISuperchainERC20Extension).interfaceId)); } /// @notice Tests that the `supportsInterface` function returns false for any other interface than the - /// `IOptimismSuperchainERC20` one. + /// `ISuperchainERC20` one. function testFuzz_supportInterface_returnFalse(bytes4 _interfaceId) public view { vm.assume(_interfaceId != type(IERC165).interfaceId); - vm.assume(_interfaceId != type(IOptimismSuperchainERC20Extension).interfaceId); + vm.assume(_interfaceId != type(ISuperchainERC20Extension).interfaceId); assertFalse(superchainERC20.supportsInterface(_interfaceId)); } } diff --git a/packages/contracts-bedrock/test/L2/SuperchainERC20Bridge.t.sol b/packages/contracts-bedrock/test/L2/SuperchainERC20Bridge.t.sol index 409b6024356d..3d1a82fffa76 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainERC20Bridge.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainERC20Bridge.t.sol @@ -10,14 +10,14 @@ import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomai // Target contract import { ISuperchainERC20Bridge } from "src/L2/interfaces/ISuperchainERC20Bridge.sol"; -import { IOptimismSuperchainERC20 } from "src/L2/interfaces/IOptimismSuperchainERC20.sol"; +import { ISuperchainERC20 } from "src/L2/interfaces/ISuperchainERC20.sol"; import { IOptimismSuperchainERC20Factory } from "src/L2/interfaces/IOptimismSuperchainERC20Factory.sol"; /// @title SuperchainERC20BridgeTest /// @notice Contract for testing the SuperchainERC20Bridge contract. contract SuperchainERC20BridgeTest is Bridge_Initializer { address internal constant ZERO_ADDRESS = address(0); - string internal constant NAME = "OptimismSuperchainERC20"; + string internal constant NAME = "SuperchainERC20"; string internal constant SYMBOL = "OSE"; address internal constant REMOTE_TOKEN = address(0x123); @@ -29,14 +29,14 @@ contract SuperchainERC20BridgeTest is Bridge_Initializer { event RelayERC20(address indexed token, address indexed from, address indexed to, uint256 amount, uint256 source); - IOptimismSuperchainERC20 public superchainERC20; + ISuperchainERC20 public superchainERC20; /// @notice Sets up the test suite. function setUp() public override { super.enableInterop(); super.setUp(); - superchainERC20 = IOptimismSuperchainERC20( + superchainERC20 = ISuperchainERC20( IOptimismSuperchainERC20Factory(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY).deploy( REMOTE_TOKEN, NAME, SYMBOL, 18 ) @@ -57,7 +57,7 @@ contract SuperchainERC20BridgeTest is Bridge_Initializer { // Mint some tokens to the sender so then they can be sent vm.prank(Predeploys.SUPERCHAIN_ERC20_BRIDGE); - superchainERC20.mint(_sender, _amount); + superchainERC20.__superchainMint(_sender, _amount); // Get the total supply and balance of `_sender` before the send to compare later on the assertions uint256 _totalSupplyBefore = superchainERC20.totalSupply(); diff --git a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/OptimismSuperchainERC20.t.sol b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/OptimismSuperchainERC20.t.sol index d90e90a2f811..915fe0b44200 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/OptimismSuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/OptimismSuperchainERC20.t.sol @@ -9,7 +9,7 @@ import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; // Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; -import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; +import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; import { ProtocolGuided } from "./fuzz/Protocol.guided.t.sol"; import { ProtocolUnguided } from "./fuzz/Protocol.unguided.t.sol"; @@ -42,7 +42,7 @@ contract OptimismSuperchainERC20Properties is Test { for (uint256 validChainId = 0; validChainId < handler.MAX_CHAINS(); validChainId++) { address supertoken = MESSENGER.superTokenAddresses(validChainId, currentSalt); if (supertoken != address(0)) { - totalSupply += OptimismSuperchainERC20(supertoken).totalSupply(); + totalSupply += SuperchainERC20(supertoken).totalSupply(); } } assertEq(trackedSupply, totalSupply + fundsInTransit); @@ -65,7 +65,7 @@ contract OptimismSuperchainERC20Properties is Test { for (uint256 validChainId = 0; validChainId < handler.MAX_CHAINS(); validChainId++) { address supertoken = MESSENGER.superTokenAddresses(validChainId, currentSalt); if (supertoken != address(0)) { - totalSupply += OptimismSuperchainERC20(supertoken).totalSupply(); + totalSupply += SuperchainERC20(supertoken).totalSupply(); } } assertEq(trackedSupply, totalSupply); @@ -73,7 +73,7 @@ contract OptimismSuperchainERC20Properties is Test { } /// @custom:invariant many other assertion mode invariants are also defined under - /// `test/invariants/OptimismSuperchainERC20/fuzz/` . + /// `test/invariants/SuperchainERC20/fuzz/` . /// /// since setting`fail_on_revert=false` also ignores StdAssertion failures, this invariant explicitly asks the /// handler for assertion test failures diff --git a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.guided.t.sol b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.guided.t.sol index b1f2d8451240..236c41873b6c 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.guided.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.guided.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.25; import { MockL2ToL2CrossDomainMessenger } from "../helpers/MockL2ToL2CrossDomainMessenger.t.sol"; -import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; +import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; import { ProtocolHandler } from "../handlers/Protocol.t.sol"; import { EnumerableMap } from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import { CompatibleAssert } from "../helpers/CompatibleAssert.t.sol"; @@ -21,7 +21,7 @@ contract ProtocolGuided is ProtocolHandler, CompatibleAssert { validateTokenDeployParams(params) { chainId = bound(chainId, 0, MAX_CHAINS - 1); - OptimismSuperchainERC20 supertoken = _deploySupertoken( + SuperchainERC20 supertoken = _deploySupertoken( remoteTokens[params.remoteTokenIndex], WORDS[params.nameIndex], WORDS[params.symbolIndex], @@ -41,7 +41,7 @@ contract ProtocolGuided is ProtocolHandler, CompatibleAssert { /// @custom:property calls to relayERC20 always succeed as long as the cross-domain caller is valid function fuzz_relayERC20(uint256 messageIndex) external { MockL2ToL2CrossDomainMessenger.CrossChainMessage memory messageToRelay = MESSENGER.messageQueue(messageIndex); - OptimismSuperchainERC20 destinationToken = OptimismSuperchainERC20(messageToRelay.crossDomainMessageSender); + SuperchainERC20 destinationToken = SuperchainERC20(messageToRelay.crossDomainMessageSender); uint256 destinationSupplyBefore = destinationToken.totalSupply(); uint256 destinationBalanceBefore = destinationToken.balanceOf(messageToRelay.recipient); diff --git a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.unguided.t.sol b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.unguided.t.sol index ccd9b91d9994..780cb2fb7788 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.unguided.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.unguided.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.25; import { ProtocolHandler } from "../handlers/Protocol.t.sol"; import { EnumerableMap } from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; -import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; +import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; import { CompatibleAssert } from "../helpers/CompatibleAssert.t.sol"; // TODO: add fuzz_sendERC20 when we implement non-atomic bridging @@ -15,9 +15,9 @@ contract ProtocolUnguided is ProtocolHandler, CompatibleAssert { function fuzz_mint(uint256 tokenIndex, address to, address sender, uint256 amount) external { address token = allSuperTokens[bound(tokenIndex, 0, allSuperTokens.length)]; bytes32 salt = MESSENGER.superTokenInitDeploySalts(token); - amount = bound(amount, 0, type(uint256).max - OptimismSuperchainERC20(token).totalSupply()); + amount = bound(amount, 0, type(uint256).max - SuperchainERC20(token).totalSupply()); vm.prank(sender); - try OptimismSuperchainERC20(token).mint(to, amount) { + try SuperchainERC20(token).__superchainMint(to, amount) { compatibleAssert(sender == BRIDGE); (, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(salt); ghost_totalSupplyAcrossChains.set(salt, currentValue + amount); @@ -31,9 +31,9 @@ contract ProtocolUnguided is ProtocolHandler, CompatibleAssert { function fuzz_burn(uint256 tokenIndex, address from, address sender, uint256 amount) external { address token = allSuperTokens[bound(tokenIndex, 0, allSuperTokens.length)]; bytes32 salt = MESSENGER.superTokenInitDeploySalts(token); - uint256 senderBalance = OptimismSuperchainERC20(token).balanceOf(sender); + uint256 senderBalance = SuperchainERC20(token).balanceOf(sender); vm.prank(sender); - try OptimismSuperchainERC20(token).burn(from, amount) { + try SuperchainERC20(token).__superchainBurn(from, amount) { compatibleAssert(sender == BRIDGE); (, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(salt); ghost_totalSupplyAcrossChains.set(salt, currentValue - amount); @@ -56,7 +56,7 @@ contract ProtocolUnguided is ProtocolHandler, CompatibleAssert { { vm.prank(sender); // revert is possible in bound, but is not part of the external call - try OptimismSuperchainERC20(allSuperTokens[bound(tokenIndex, 0, allSuperTokens.length)]).initialize( + try SuperchainERC20(allSuperTokens[bound(tokenIndex, 0, allSuperTokens.length)]).initialize( remoteToken, name, symbol, decimals ) { compatibleAssert(false); diff --git a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/handlers/Protocol.t.sol b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/handlers/Protocol.t.sol index 921495b467ab..554172a5c15b 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/handlers/Protocol.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/handlers/Protocol.t.sol @@ -6,7 +6,7 @@ import { StdUtils } from "forge-std/StdUtils.sol"; import { ERC1967Proxy } from "@openzeppelin/contracts-v5/proxy/ERC1967/ERC1967Proxy.sol"; import { EnumerableMap } from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; -import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; +import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; import { OptimismSuperchainERC20ForToBProperties } from "../helpers/OptimismSuperchainERC20ForToBProperties.t.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; import { MockL2ToL2CrossDomainMessenger } from "../helpers/MockL2ToL2CrossDomainMessenger.t.sol"; @@ -22,7 +22,7 @@ contract ProtocolHandler is TestBase, StdUtils, Actors { address internal constant BRIDGE = Predeploys.L2_STANDARD_BRIDGE; MockL2ToL2CrossDomainMessenger internal constant MESSENGER = MockL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); - OptimismSuperchainERC20 internal superchainERC20Impl; + SuperchainERC20 internal superchainERC20Impl; // NOTE: having more options for this enables the fuzzer to configure // different supertokens for the same remote token string[] internal WORDS = ["TOKENS"]; @@ -79,7 +79,7 @@ contract ProtocolHandler is TestBase, StdUtils, Actors { index = bound(index, 0, allSuperTokens.length - 1); address addr = allSuperTokens[index]; vm.prank(BRIDGE); - OptimismSuperchainERC20(addr).mint(currentActor(), amount); + SuperchainERC20(addr).__superchainMint(currentActor(), amount); // currentValue will be zero if key is not present (, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(MESSENGER.superTokenInitDeploySalts(addr)); ghost_totalSupplyAcrossChains.set(MESSENGER.superTokenInitDeploySalts(addr), currentValue + amount); @@ -97,7 +97,7 @@ contract ProtocolHandler is TestBase, StdUtils, Actors { withActor(msg.sender) { vm.prank(currentActor()); - OptimismSuperchainERC20(allSuperTokens[bound(tokenIndex, 0, allSuperTokens.length)]).transfer( + SuperchainERC20(allSuperTokens[bound(tokenIndex, 0, allSuperTokens.length)]).transfer( getActorByRawIndex(toIndex), amount ); } @@ -112,7 +112,7 @@ contract ProtocolHandler is TestBase, StdUtils, Actors { withActor(msg.sender) { vm.prank(currentActor()); - OptimismSuperchainERC20(allSuperTokens[bound(tokenIndex, 0, allSuperTokens.length)]).transferFrom( + SuperchainERC20(allSuperTokens[bound(tokenIndex, 0, allSuperTokens.length)]).transferFrom( getActorByRawIndex(fromIndex), getActorByRawIndex(toIndex), amount ); } @@ -126,7 +126,7 @@ contract ProtocolHandler is TestBase, StdUtils, Actors { withActor(msg.sender) { vm.prank(currentActor()); - OptimismSuperchainERC20(allSuperTokens[bound(tokenIndex, 0, allSuperTokens.length)]).approve( + SuperchainERC20(allSuperTokens[bound(tokenIndex, 0, allSuperTokens.length)]).approve( getActorByRawIndex(spenderIndex), amount ); } @@ -151,7 +151,7 @@ contract ProtocolHandler is TestBase, StdUtils, Actors { uint256 chainId ) internal - returns (OptimismSuperchainERC20 supertoken) + returns (SuperchainERC20 supertoken) { // this salt would be used in production. Tokens sharing it will be bridgable with each other bytes32 realSalt = keccak256(abi.encode(remoteToken, name, symbol, decimals)); @@ -162,12 +162,12 @@ contract ProtocolHandler is TestBase, StdUtils, Actors { // what we use in the tests to walk around two contracts needing two different addresses // tbf we could be using CREATE1, but this feels more verbose bytes32 hackySalt = keccak256(abi.encode(remoteToken, name, symbol, decimals, chainId)); - supertoken = OptimismSuperchainERC20( + supertoken = SuperchainERC20( address( // TODO: Use the SuperchainERC20 Beacon Proxy new ERC1967Proxy{ salt: hackySalt }( address(superchainERC20Impl), - abi.encodeCall(OptimismSuperchainERC20.initialize, (remoteToken, name, symbol, decimals)) + abi.encodeCall(SuperchainERC20.initialize, (remoteToken, name, symbol, decimals)) ) ) ); diff --git a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/helpers/MockL2ToL2CrossDomainMessenger.t.sol b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/helpers/MockL2ToL2CrossDomainMessenger.t.sol index 6eb1c30e6799..b5d70596ed63 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/helpers/MockL2ToL2CrossDomainMessenger.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/helpers/MockL2ToL2CrossDomainMessenger.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; +import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; import { SafeCall } from "src/libraries/SafeCall.sol"; contract MockL2ToL2CrossDomainMessenger { @@ -41,9 +41,9 @@ contract MockL2ToL2CrossDomainMessenger { ) external view - returns (OptimismSuperchainERC20) + returns (SuperchainERC20) { - return OptimismSuperchainERC20(superTokenAddresses[destinationChainId][superTokenInitDeploySalts[sender]]); + return SuperchainERC20(superTokenAddresses[destinationChainId][superTokenInitDeploySalts[sender]]); } function setCrossDomainMessageSender(address sender) external { diff --git a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/helpers/OptimismSuperchainERC20ForToBProperties.t.sol b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/helpers/OptimismSuperchainERC20ForToBProperties.t.sol index 9f80cda92fcc..7342fc0b1d20 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/helpers/OptimismSuperchainERC20ForToBProperties.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/helpers/OptimismSuperchainERC20ForToBProperties.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: AGPL-3 pragma solidity ^0.8.25; -import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; +import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; -contract OptimismSuperchainERC20ForToBProperties is OptimismSuperchainERC20 { +contract OptimismSuperchainERC20ForToBProperties is SuperchainERC20 { /// @notice This is used by CryticERC20ExternalBasicProperties (only used /// in Medusa testing campaign)to know which properties to test, and /// remains here so Medusa and Foundry test campaigns can use a single diff --git a/packages/contracts-bedrock/test/vendor/Initializable.t.sol b/packages/contracts-bedrock/test/vendor/Initializable.t.sol index d5c1a9e5e4c3..0228c7bb53b5 100644 --- a/packages/contracts-bedrock/test/vendor/Initializable.t.sol +++ b/packages/contracts-bedrock/test/vendor/Initializable.t.sol @@ -401,7 +401,7 @@ contract Initializer_Test is Bridge_Initializer { excludes[0] = "src/L1/SystemConfigInterop.sol"; excludes[1] = "src/L1/OptimismPortalInterop.sol"; // Contract is currently not being deployed as part of the standard deployment script. - excludes[2] = "src/L2/OptimismSuperchainERC20.sol"; + excludes[2] = "src/L2/SuperchainERC20.sol"; // Periphery contracts don't get deployed as part of the standard deployment script. excludes[3] = "src/periphery/*"; // TODO: Deployment script is currently "broken" in the sense that it doesn't properly diff --git a/packages/contracts-bedrock/test/vendor/InitializableOZv5.t.sol b/packages/contracts-bedrock/test/vendor/InitializableOZv5.t.sol index 0820f987414a..e23d50927229 100644 --- a/packages/contracts-bedrock/test/vendor/InitializableOZv5.t.sol +++ b/packages/contracts-bedrock/test/vendor/InitializableOZv5.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.25; import { Test } from "forge-std/Test.sol"; -import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; +import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; import { Initializable } from "@openzeppelin/contracts-v5/proxy/utils/Initializable.sol"; /// @title InitializerOZv5_Test @@ -28,11 +28,11 @@ contract InitializerOZv5_Test is Test { // Initialize the `contracts` array with the addresses of the contracts to test and the // calldata used to initialize them - // OptimismSuperchainERC20 + // SuperchainERC20 contracts.push( InitializeableContract({ - target: address(new OptimismSuperchainERC20()), - initCalldata: abi.encodeCall(OptimismSuperchainERC20.initialize, (address(0), "", "", 18)) + target: address(new SuperchainERC20()), + initCalldata: abi.encodeCall(SuperchainERC20.initialize, (address(0), "", "", 18)) }) ); } From ff47523563209c9a9a3a9930b9e6ab16cfefc66e Mon Sep 17 00:00:00 2001 From: 0xDiscotech <131301107+0xDiscotech@users.noreply.github.com> Date: Tue, 1 Oct 2024 11:27:47 -0300 Subject: [PATCH 02/10] feat: create optimism superchain erc20 contract * chore: update natspec and errors --- .../src/L2/OptimismSuperchainERC20.sol | 47 +++++++++++++++++++ .../src/L2/SuperchainERC20.sol | 24 +++++----- .../interfaces/IOptimismSuperchainERC20.sol | 31 ++++++++++++ .../src/L2/interfaces/ISuperchainERC20.sol | 9 ++-- .../test/L2/SuperchainERC20.t.sol | 8 ++-- 5 files changed, 98 insertions(+), 21 deletions(-) create mode 100644 packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol create mode 100644 packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol diff --git a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol new file mode 100644 index 000000000000..f4c4ddf4b753 --- /dev/null +++ b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import { Predeploys } from "src/libraries/Predeploys.sol"; +import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; +import { IOptimismSuperchainERC20Extension } from "src/L2/interfaces/IOptimismSuperchainERC20.sol"; + +/// @custom:proxied true +/// @title OptimismSuperchainERC20 +/// @notice OptimismSuperchainERC20 is a standard extension of the base ERC20 token contract that unifies ERC20 token +/// bridging to make it fungible across the Superchain. This construction allows the L2StandardBridge to burn +/// and mint tokens. This makes it possible to convert a valid OptimismMintableERC20 token to a +/// OptimismSuperchainERC20 token, turning it fungible and interoperable across the superchain. Likewise, it +/// also enables the inverse conversion path. +/// Moreover, it builds on top of the L2ToL2CrossDomainMessenger for both replay protection and domain binding. +contract OptimismSuperchainERC20 is SuperchainERC20, IOptimismSuperchainERC20Extension { + /// @notice Semantic version. + /// @custom:semver 1.0.0-beta + string public constant override version = "1.0.0-beta"; + + modifier onlyL2StandardBridge() { + if (msg.sender != Predeploys.L2_STANDARD_BRIDGE) revert OnlyL2StandardBridge(); + _; + } + + /// @notice Allows the L2StandardBridge and SuperchainERC20Bridge 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 onlyL2StandardBridge { + if (_to == address(0)) revert ZeroAddress(); + + _mint(_to, _amount); + + emit Burn(_to, _amount); + } + + /// @notice Allows the L2StandardBridge and SuperchainERC20Bridge 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 onlyL2StandardBridge { + if (_from == address(0)) revert ZeroAddress(); + + _burn(_from, _amount); + + emit Burn(_from, _amount); + } +} diff --git a/packages/contracts-bedrock/src/L2/SuperchainERC20.sol b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol index 2f5e48912b89..673e5489b982 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol @@ -44,22 +44,22 @@ contract SuperchainERC20 is ERC20, Initializable, ERC165, ISuperchainERC20Extens } /// @notice A modifier that only allows the bridge to call - modifier onlyAuthorizedBridge() { - if (msg.sender != Predeploys.L2_STANDARD_BRIDGE && msg.sender != Predeploys.SUPERCHAIN_ERC20_BRIDGE) { - revert OnlyAuthorizedBridge(); - } + modifier onlySuperchainERC20Bridge() { + if (msg.sender != Predeploys.SUPERCHAIN_ERC20_BRIDGE) revert OnlySuperchainERC20Bridge(); _; } - /// @notice Semantic version. - /// @custom:semver 1.0.0-beta.6 - string public constant version = "1.0.0-beta.6"; - /// @notice Constructs the SuperchainERC20 contract. constructor() { _disableInitializers(); } + /// @notice Semantic version. + /// @custom:semver 1.0.0-beta.6 + function version() external pure virtual returns (string memory) { + return "1.0.0-beta.6"; + } + /// @notice Initializes the contract. /// @param _remoteToken Address of the corresponding remote token. /// @param _name ERC20 name. @@ -81,10 +81,10 @@ contract SuperchainERC20 is ERC20, Initializable, ERC165, ISuperchainERC20Extens _storage.decimals = _decimals; } - /// @notice Allows the L2StandardBridge and SuperchainERC20Bridge to mint tokens. + /// @notice Allows the SuperchainERC20Bridge to mint tokens. /// @param _to Address to mint tokens to. /// @param _amount Amount of tokens to mint. - function __superchainMint(address _to, uint256 _amount) external virtual onlyAuthorizedBridge { + function __superchainMint(address _to, uint256 _amount) external virtual onlySuperchainERC20Bridge { if (_to == address(0)) revert ZeroAddress(); _mint(_to, _amount); @@ -92,10 +92,10 @@ contract SuperchainERC20 is ERC20, Initializable, ERC165, ISuperchainERC20Extens emit Mint(_to, _amount); } - /// @notice Allows the L2StandardBridge and SuperchainERC20Bridge to burn tokens. + /// @notice Allows the SuperchainERC20Bridge to burn tokens. /// @param _from Address to burn tokens from. /// @param _amount Amount of tokens to burn. - function __superchainBurn(address _from, uint256 _amount) external virtual onlyAuthorizedBridge { + function __superchainBurn(address _from, uint256 _amount) external virtual onlySuperchainERC20Bridge { if (_from == address(0)) revert ZeroAddress(); _burn(_from, _amount); diff --git a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol new file mode 100644 index 000000000000..f3cb7fd1ee43 --- /dev/null +++ b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// Interfaces +import { ISuperchainERC20, ISuperchainERC20Extension } from "src/L2/interfaces/ISuperchainERC20.sol"; +import { IERC20Solady } from "src/vendor/interfaces/IERC20Solady.sol"; + +/// @title ISuperchainERC20Errors +/// @notice Interface containing the errors added in the SuperchainERC20 implementation. +interface IOptimismSuperchainERC20Errors { + /// @notice Thrown when attempting to mint or burn tokens and the function caller is not the L2StandardBridge + error OnlyL2StandardBridge(); +} + +/// @title ISuperchainERC20Extension +/// @notice This interface is available on the SuperchainERC20 contract. +interface IOptimismSuperchainERC20Extension is ISuperchainERC20Extension, IOptimismSuperchainERC20Errors { + /// @notice Allows the L2StandardBridge and SuperchainERC20Bridge to mint tokens. + /// @param _to Address to mint tokens to. + /// @param _amount Amount of tokens to mint. + function mint(address _to, uint256 _amount) external; + + /// @notice Allows the L2StandardBridge and SuperchainERC20Bridge to burn tokens. + /// @param _from Address to burn tokens from. + /// @param _amount Amount of tokens to burn. + function burn(address _from, uint256 _amount) external; +} + +/// @title ISuperchainERC20 +/// @notice Combines Solady's ERC20 interface with the SuperchainERC20Extension interface. +interface IOptimismSuperchainERC20 is IERC20Solady, IOptimismSuperchainERC20Extension { } diff --git a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol index 2035b37f9397..98267924e711 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol @@ -10,9 +10,8 @@ interface ISuperchainERC20Errors { /// @notice Thrown when attempting to perform an operation and the account is the zero address. error ZeroAddress(); - /// @notice Thrown when attempting to mint or burn tokens and the function caller is not the StandardBridge or the - /// SuperchainERC20Bridge. - error OnlyAuthorizedBridge(); + /// @notice Thrown when attempting to mint or burn tokens and the function caller is not the SuperchainERC20Bridge. + error OnlySuperchainERC20Bridge(); } /// @title ISuperchainERC20Extension @@ -28,12 +27,12 @@ interface ISuperchainERC20Extension is ISuperchainERC20Errors { /// @param amount Amount of tokens burned. event Burn(address indexed account, uint256 amount); - /// @notice Allows the L2StandardBridge and SuperchainERC20Bridge to mint tokens. + /// @notice Allows the SuperchainERC20Bridge to mint tokens. /// @param _to Address to mint tokens to. /// @param _amount Amount of tokens to mint. function __superchainMint(address _to, uint256 _amount) external; - /// @notice Allows the L2StandardBridge and SuperchainERC20Bridge to burn tokens. + /// @notice Allows the SuperchainERC20Bridge to burn tokens. /// @param _from Address to burn tokens from. /// @param _amount Amount of tokens to burn. function __superchainBurn(address _from, uint256 _amount) external; diff --git a/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol index 5a4242b6dd54..69347cc6d367 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol @@ -125,8 +125,8 @@ contract SuperchainERC20Test is Test { vm.assume(_caller != L2_BRIDGE); vm.assume(_caller != SUPERCHAIN_ERC20_BRIDGE); - // Expect the revert with `OnlyAuthorizedBridge` selector - vm.expectRevert(ISuperchainERC20Errors.OnlyAuthorizedBridge.selector); + // Expect the revert with `OnlySuperchainERC20Bridge` selector + vm.expectRevert(ISuperchainERC20Errors.OnlySuperchainERC20Bridge.selector); // Call the `mint` function with the non-bridge caller vm.prank(_caller); @@ -177,8 +177,8 @@ contract SuperchainERC20Test is Test { vm.assume(_caller != L2_BRIDGE); vm.assume(_caller != SUPERCHAIN_ERC20_BRIDGE); - // Expect the revert with `OnlyAuthorizedBridge` selector - vm.expectRevert(ISuperchainERC20Errors.OnlyAuthorizedBridge.selector); + // Expect the revert with `OnlySuperchainERC20Bridge` selector + vm.expectRevert(ISuperchainERC20Errors.OnlySuperchainERC20Bridge.selector); // Call the `burn` function with the non-bridge caller vm.prank(_caller); From 5c31ce7f2a440d219915cc86aae7489f0cba0016 Mon Sep 17 00:00:00 2001 From: 0xDiscotech <131301107+0xDiscotech@users.noreply.github.com> Date: Tue, 1 Oct 2024 11:32:28 -0300 Subject: [PATCH 03/10] fix: superchain erc20 tests --- .../test/L2/SuperchainERC20.t.sol | 32 ++++++------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol index 69347cc6d367..c4f02be6ad57 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol @@ -27,7 +27,6 @@ contract SuperchainERC20Test is Test { string internal constant NAME = "SuperchainERC20"; string internal constant SYMBOL = "SCE"; uint8 internal constant DECIMALS = 18; - address internal constant L2_BRIDGE = Predeploys.L2_STANDARD_BRIDGE; address internal constant SUPERCHAIN_ERC20_BRIDGE = Predeploys.SUPERCHAIN_ERC20_BRIDGE; address internal constant MESSENGER = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER; @@ -83,12 +82,6 @@ contract SuperchainERC20Test is Test { ); } - /// @notice Helper function to fuzz the bridge address to performs the calls with. - /// @dev Needed to cover both possible branches of the authorized callers on `mint` and `burn` functions. - function _getBridge(bool _returnL2StandardBridge) internal pure returns (address bridge) { - bridge = _returnL2StandardBridge ? L2_BRIDGE : SUPERCHAIN_ERC20_BRIDGE; - } - /// @notice Helper function to setup a mock and expect a call to it. function _mockAndExpect(address _receiver, bytes memory _calldata, bytes memory _returned) internal { vm.mockCall(_receiver, _calldata, _returned); @@ -122,7 +115,6 @@ contract SuperchainERC20Test is Test { /// @notice Tests the `mint` function reverts when the caller is not the bridge. function testFuzz_mint_callerNotBridge_reverts(address _caller, address _to, uint256 _amount) public { // Ensure the caller is not the bridge - vm.assume(_caller != L2_BRIDGE); vm.assume(_caller != SUPERCHAIN_ERC20_BRIDGE); // Expect the revert with `OnlySuperchainERC20Bridge` selector @@ -134,18 +126,17 @@ contract SuperchainERC20Test is Test { } /// @notice Tests the `mint` function reverts when the amount is zero. - function testFuzz_mint_zeroAddressTo_reverts(uint256 _amount, bool _returnL2StandardBridge) public { + function testFuzz_mint_zeroAddressTo_reverts(uint256 _amount) public { // Expect the revert with `ZeroAddress` selector vm.expectRevert(ISuperchainERC20Errors.ZeroAddress.selector); // Call the `mint` function with the zero address - address bridge = _getBridge(_returnL2StandardBridge); - vm.prank(bridge); + vm.prank(SUPERCHAIN_ERC20_BRIDGE); superchainERC20.__superchainMint({ _to: ZERO_ADDRESS, _amount: _amount }); } /// @notice Tests the `mint` succeeds and emits the `Mint` event. - function testFuzz_mint_succeeds(address _to, uint256 _amount, bool _returnL2StandardBridge) public { + function testFuzz_mint_succeeds(address _to, uint256 _amount) public { // Ensure `_to` is not the zero address vm.assume(_to != ZERO_ADDRESS); @@ -162,8 +153,7 @@ contract SuperchainERC20Test is Test { emit ISuperchainERC20Extension.Mint(_to, _amount); // Call the `mint` function with the bridge caller - address bridge = _getBridge(_returnL2StandardBridge); - vm.prank(bridge); + vm.prank(SUPERCHAIN_ERC20_BRIDGE); superchainERC20.__superchainMint(_to, _amount); // Check the total supply and balance of `_to` after the mint were updated correctly @@ -174,7 +164,6 @@ contract SuperchainERC20Test is Test { /// @notice Tests the `burn` function reverts when the caller is not the bridge. function testFuzz_burn_callerNotBridge_reverts(address _caller, address _from, uint256 _amount) public { // Ensure the caller is not the bridge - vm.assume(_caller != L2_BRIDGE); vm.assume(_caller != SUPERCHAIN_ERC20_BRIDGE); // Expect the revert with `OnlySuperchainERC20Bridge` selector @@ -186,24 +175,22 @@ contract SuperchainERC20Test is Test { } /// @notice Tests the `burn` function reverts when the amount is zero. - function testFuzz_burn_zeroAddressFrom_reverts(uint256 _amount, bool _returnL2StandardBridge) public { + function testFuzz_burn_zeroAddressFrom_reverts(uint256 _amount) public { // Expect the revert with `ZeroAddress` selector vm.expectRevert(ISuperchainERC20Errors.ZeroAddress.selector); // Call the `burn` function with the zero address - address bridge = _getBridge(_returnL2StandardBridge); - vm.prank(bridge); + vm.prank(SUPERCHAIN_ERC20_BRIDGE); superchainERC20.__superchainBurn({ _from: ZERO_ADDRESS, _amount: _amount }); } /// @notice Tests the `burn` burns the amount and emits the `Burn` event. - function testFuzz_burn_succeeds(address _from, uint256 _amount, bool _returnL2StandardBridge) public { + function testFuzz_burn_succeeds(address _from, uint256 _amount) public { // Ensure `_from` is not the zero address vm.assume(_from != ZERO_ADDRESS); // Mint some tokens to `_from` so then they can be burned - address bridge = _getBridge(_returnL2StandardBridge); - vm.prank(bridge); + vm.prank(SUPERCHAIN_ERC20_BRIDGE); superchainERC20.__superchainMint(_from, _amount); // Get the total supply and balance of `_from` before the burn to compare later on the assertions @@ -219,8 +206,7 @@ contract SuperchainERC20Test is Test { emit ISuperchainERC20Extension.Burn(_from, _amount); // Call the `burn` function with the bridge caller - bridge = _getBridge(_returnL2StandardBridge); - vm.prank(bridge); + vm.prank(SUPERCHAIN_ERC20_BRIDGE); superchainERC20.__superchainBurn(_from, _amount); // Check the total supply and balance of `_from` after the burn were updated correctly From 841101d499dd85cda9e9afe9ab8e5ceaec8373c7 Mon Sep 17 00:00:00 2001 From: 0xDiscotech <131301107+0xDiscotech@users.noreply.github.com> Date: Tue, 1 Oct 2024 13:18:43 -0300 Subject: [PATCH 04/10] refactor: make superchain erc20 abstract * refactor: move storage and erc20 metadata functions to implementation * chore: update interfaces --- .../contracts-bedrock/scripts/L2Genesis.s.sol | 4 +- .../src/L2/OptimismSuperchainERC20.sol | 88 +++++- .../src/L2/OptimismSuperchainERC20Factory.sol | 4 +- .../src/L2/SuperchainERC20.sol | 89 +----- .../interfaces/IOptimismSuperchainERC20.sol | 3 + .../src/L2/interfaces/ISuperchainERC20.sol | 3 - .../test/L2/OptimismSuperchainERC20.t.sol | 261 ++++++++++++++++++ .../L2/OptimismSuperchainERC20Factory.t.sol | 10 +- .../test/L2/SuperchainERC20.t.sol | 141 ++-------- .../fuzz/Protocol.unguided.t.sol | 12 +- .../handlers/Protocol.t.sol | 20 +- ...imismSuperchainERC20ForToBProperties.t.sol | 4 +- .../test/vendor/InitializableOZv5.t.sol | 8 +- 13 files changed, 406 insertions(+), 241 deletions(-) create mode 100644 packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol diff --git a/packages/contracts-bedrock/scripts/L2Genesis.s.sol b/packages/contracts-bedrock/scripts/L2Genesis.s.sol index ebc0c4a2dba9..f93ab0e3b718 100644 --- a/packages/contracts-bedrock/scripts/L2Genesis.s.sol +++ b/packages/contracts-bedrock/scripts/L2Genesis.s.sol @@ -542,8 +542,8 @@ contract L2Genesis is Deployer { /// @notice This predeploy is following the safety invariant #2. function setOptimismSuperchainERC20Beacon() internal { address superchainERC20Impl = Predeploys.OPTIMISM_SUPERCHAIN_ERC20; - console.log("Setting %s implementation at: %s", "SuperchainERC20", superchainERC20Impl); - vm.etch(superchainERC20Impl, vm.getDeployedCode("SuperchainERC20.sol:SuperchainERC20")); + console.log("Setting %s implementation at: %s", "OptimismSuperchainERC20", superchainERC20Impl); + vm.etch(superchainERC20Impl, vm.getDeployedCode("OptimismSuperchainERC20.sol:OptimismSuperchainERC20")); OptimismSuperchainERC20Beacon beacon = new OptimismSuperchainERC20Beacon(superchainERC20Impl); address beaconImpl = Predeploys.predeployToCodeNamespace(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON); diff --git a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol index f4c4ddf4b753..0442e21a7fe3 100644 --- a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol @@ -4,6 +4,8 @@ pragma solidity 0.8.25; import { Predeploys } from "src/libraries/Predeploys.sol"; import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; import { IOptimismSuperchainERC20Extension } from "src/L2/interfaces/IOptimismSuperchainERC20.sol"; +import { ERC165 } from "@openzeppelin/contracts-v5/utils/introspection/ERC165.sol"; +import { Initializable } from "@openzeppelin/contracts-v5/proxy/utils/Initializable.sol"; /// @custom:proxied true /// @title OptimismSuperchainERC20 @@ -13,7 +15,58 @@ import { IOptimismSuperchainERC20Extension } from "src/L2/interfaces/IOptimismSu /// OptimismSuperchainERC20 token, turning it fungible and interoperable across the superchain. Likewise, it /// also enables the inverse conversion path. /// Moreover, it builds on top of the L2ToL2CrossDomainMessenger for both replay protection and domain binding. -contract OptimismSuperchainERC20 is SuperchainERC20, IOptimismSuperchainERC20Extension { +contract OptimismSuperchainERC20 is SuperchainERC20, Initializable, ERC165, IOptimismSuperchainERC20Extension { + /// @notice Storage slot that the SuperchainERC20Metadata struct is stored at. + /// keccak256(abi.encode(uint256(keccak256("SuperchainERC20.metadata")) - 1)) & ~bytes32(uint256(0xff)); + bytes32 internal constant OPTIMISM_SUPERCHAIN_ERC20_METADATA_SLOT = + 0x07f04e84143df95a6373fcf376312ae41da81a193a3089073a54f47a74d8fb00; + + /// @notice Storage struct for the SuperchainERC20 metadata. + /// @custom:storage-location erc7201:SuperchainERC20.metadata + struct SuperchainERC20Metadata { + /// @notice Address of the corresponding version of this token on the remote chain. + address remoteToken; + /// @notice Name of the token + string name; + /// @notice Symbol of the token + string symbol; + /// @notice Decimals of the token + uint8 decimals; + } + + /// @notice Constructs the SuperchainERC20 contract. + constructor() { + _disableInitializers(); + } + + /// @notice Returns the storage for the SuperchainERC20Metadata. + function _getStorage() internal pure returns (SuperchainERC20Metadata storage _storage) { + assembly { + _storage.slot := OPTIMISM_SUPERCHAIN_ERC20_METADATA_SLOT + } + } + + /// @notice Initializes the contract. + /// @param _remoteToken Address of the corresponding remote token. + /// @param _name ERC20 name. + /// @param _symbol ERC20 symbol. + /// @param _decimals ERC20 decimals. + function initialize( + address _remoteToken, + string memory _name, + string memory _symbol, + uint8 _decimals + ) + external + initializer + { + SuperchainERC20Metadata storage _storage = _getStorage(); + _storage.remoteToken = _remoteToken; + _storage.name = _name; + _storage.symbol = _symbol; + _storage.decimals = _decimals; + } + /// @notice Semantic version. /// @custom:semver 1.0.0-beta string public constant override version = "1.0.0-beta"; @@ -44,4 +97,37 @@ contract OptimismSuperchainERC20 is SuperchainERC20, IOptimismSuperchainERC20Ext emit Burn(_from, _amount); } + + /// @notice Returns the address of the corresponding version of this token on the remote chain. + function remoteToken() public view override returns (address) { + return _getStorage().remoteToken; + } + + /// @notice Returns the name of the token. + function name() public view virtual override returns (string memory) { + return _getStorage().name; + } + + /// @notice Returns the symbol of the token. + function symbol() public view virtual override returns (string memory) { + return _getStorage().symbol; + } + + /// @notice 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 _getStorage().decimals; + } + + /// @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) public view virtual override returns (bool) { + return + _interfaceId == type(IOptimismSuperchainERC20Extension).interfaceId || super.supportsInterface(_interfaceId); + } } diff --git a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol index 4c9b43c45af9..f34d06d8505d 100644 --- a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol +++ b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.25; import { IOptimismSuperchainERC20Factory } from "src/L2/interfaces/IOptimismSuperchainERC20Factory.sol"; import { ISemver } from "src/universal/interfaces/ISemver.sol"; -import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; +import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; import { BeaconProxy } from "@openzeppelin/contracts-v5/proxy/beacon/BeaconProxy.sol"; import { CREATE3 } from "@rari-capital/solmate/src/utils/CREATE3.sol"; @@ -46,7 +46,7 @@ contract OptimismSuperchainERC20Factory is IOptimismSuperchainERC20Factory, ISem returns (address _superchainERC20) { bytes memory initCallData = - abi.encodeCall(SuperchainERC20.initialize, (_remoteToken, _name, _symbol, _decimals)); + abi.encodeCall(OptimismSuperchainERC20.initialize, (_remoteToken, _name, _symbol, _decimals)); bytes memory creationCode = bytes.concat( type(BeaconProxy).creationCode, abi.encode(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON, initCallData) diff --git a/packages/contracts-bedrock/src/L2/SuperchainERC20.sol b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol index 673e5489b982..feb4873b1546 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol @@ -2,14 +2,10 @@ pragma solidity 0.8.25; import { ISuperchainERC20Extension } from "src/L2/interfaces/ISuperchainERC20.sol"; -import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; import { ISemver } from "src/universal/interfaces/ISemver.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; -import { ERC165 } from "@openzeppelin/contracts-v5/utils/introspection/ERC165.sol"; import { ERC20 } from "@solady/tokens/ERC20.sol"; -import { Initializable } from "@openzeppelin/contracts-v5/proxy/utils/Initializable.sol"; -/// @custom:proxied true /// @title SuperchainERC20 /// @notice SuperchainERC20 is a standard extension of the base ERC20 token contract that unifies ERC20 token /// bridging to make it fungible across the Superchain. This construction allows the L2StandardBridge to burn @@ -17,70 +13,19 @@ import { Initializable } from "@openzeppelin/contracts-v5/proxy/utils/Initializa /// SuperchainERC20 token, turning it fungible and interoperable across the superchain. Likewise, it /// also enables the inverse conversion path. /// Moreover, it builds on top of the L2ToL2CrossDomainMessenger for both replay protection and domain binding. -contract SuperchainERC20 is ERC20, Initializable, ERC165, ISuperchainERC20Extension, ISemver { - /// @notice Storage slot that the SuperchainERC20Metadata struct is stored at. - /// keccak256(abi.encode(uint256(keccak256("SuperchainERC20.metadata")) - 1)) & ~bytes32(uint256(0xff)); - bytes32 internal constant OPTIMISM_SUPERCHAIN_ERC20_METADATA_SLOT = - 0x07f04e84143df95a6373fcf376312ae41da81a193a3089073a54f47a74d8fb00; - - /// @notice Storage struct for the SuperchainERC20 metadata. - /// @custom:storage-location erc7201:SuperchainERC20.metadata - struct SuperchainERC20Metadata { - /// @notice Address of the corresponding version of this token on the remote chain. - address remoteToken; - /// @notice Name of the token - string name; - /// @notice Symbol of the token - string symbol; - /// @notice Decimals of the token - uint8 decimals; - } - - /// @notice Returns the storage for the SuperchainERC20Metadata. - function _getStorage() private pure returns (SuperchainERC20Metadata storage _storage) { - assembly { - _storage.slot := OPTIMISM_SUPERCHAIN_ERC20_METADATA_SLOT - } - } - +abstract contract SuperchainERC20 is ERC20, ISuperchainERC20Extension, ISemver { /// @notice A modifier that only allows the bridge to call modifier onlySuperchainERC20Bridge() { if (msg.sender != Predeploys.SUPERCHAIN_ERC20_BRIDGE) revert OnlySuperchainERC20Bridge(); _; } - /// @notice Constructs the SuperchainERC20 contract. - constructor() { - _disableInitializers(); - } - /// @notice Semantic version. /// @custom:semver 1.0.0-beta.6 function version() external pure virtual returns (string memory) { return "1.0.0-beta.6"; } - /// @notice Initializes the contract. - /// @param _remoteToken Address of the corresponding remote token. - /// @param _name ERC20 name. - /// @param _symbol ERC20 symbol. - /// @param _decimals ERC20 decimals. - function initialize( - address _remoteToken, - string memory _name, - string memory _symbol, - uint8 _decimals - ) - external - initializer - { - SuperchainERC20Metadata storage _storage = _getStorage(); - _storage.remoteToken = _remoteToken; - _storage.name = _name; - _storage.symbol = _symbol; - _storage.decimals = _decimals; - } - /// @notice Allows the SuperchainERC20Bridge to mint tokens. /// @param _to Address to mint tokens to. /// @param _amount Amount of tokens to mint. @@ -102,36 +47,4 @@ contract SuperchainERC20 is ERC20, Initializable, ERC165, ISuperchainERC20Extens emit Burn(_from, _amount); } - - /// @notice Returns the address of the corresponding version of this token on the remote chain. - function remoteToken() public view override returns (address) { - return _getStorage().remoteToken; - } - - /// @notice Returns the name of the token. - function name() public view virtual override returns (string memory) { - return _getStorage().name; - } - - /// @notice Returns the symbol of the token. - function symbol() public view virtual override returns (string memory) { - return _getStorage().symbol; - } - - /// @notice 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 _getStorage().decimals; - } - - /// @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) public view virtual override returns (bool) { - return _interfaceId == type(ISuperchainERC20Extension).interfaceId || super.supportsInterface(_interfaceId); - } } diff --git a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol index f3cb7fd1ee43..8f2111ba9d0d 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol @@ -24,6 +24,9 @@ interface IOptimismSuperchainERC20Extension is ISuperchainERC20Extension, IOptim /// @param _from Address to burn tokens from. /// @param _amount Amount of tokens to burn. function burn(address _from, uint256 _amount) external; + + /// @notice Returns the address of the corresponding version of this token on the remote chain. + function remoteToken() external view returns (address); } /// @title ISuperchainERC20 diff --git a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol index 98267924e711..149fde37a03a 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol @@ -36,9 +36,6 @@ interface ISuperchainERC20Extension is ISuperchainERC20Errors { /// @param _from Address to burn tokens from. /// @param _amount Amount of tokens to burn. function __superchainBurn(address _from, uint256 _amount) external; - - /// @notice Returns the address of the corresponding version of this token on the remote chain. - function remoteToken() external view returns (address); } /// @title ISuperchainERC20 diff --git a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol new file mode 100644 index 000000000000..70cce6f907a0 --- /dev/null +++ b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol @@ -0,0 +1,261 @@ +// // 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 { IERC20 } from "@openzeppelin/contracts-v5/token/ERC20/IERC20.sol"; +// import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; +// 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 { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; +// import { ISuperchainERC20Errors } from "src/L2/interfaces/ISuperchainERC20.sol"; + +// /// @title OptimismSuperchainERC20Test +// /// @notice Contract for testing the OptimismSuperchainERC20 contract. +// contract OptimismSuperchainERC20Test is Test { +// address internal constant ZERO_ADDRESS = address(0); +// address internal constant REMOTE_TOKEN = address(0x123); +// string internal constant NAME = "OptimismSuperchainERC20"; +// string internal constant SYMBOL = "OSC"; +// uint8 internal constant DECIMALS = 18; +// address internal constant L2_BRIDGE = Predeploys.L2_STANDARD_BRIDGE; +// address internal constant MESSENGER = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER; + +// OptimismSuperchainERC20 public optimismSuperchainERC20Impl; +// OptimismSuperchainERC20 public optimismSuperchainERC20; + +// /// @notice Sets up the test suite. +// function setUp() public { +// optimismSuperchainERC20Impl = new OptimismSuperchainERC20(); + +// // Deploy the OptimismSuperchainERC20Beacon contract +// _deployBeacon(); + +// optimismSuperchainERC20 = _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(optimismSuperchainERC20Impl)) +// ); +// } + +// /// @notice Helper function to deploy a proxy of the OptimismSuperchainERC20 contract. +// function _deploySuperchainERC20Proxy( +// address _remoteToken, +// string memory _name, +// string memory _symbol, +// uint8 _decimals +// ) +// internal +// returns (OptimismSuperchainERC20) +// { +// return OptimismSuperchainERC20( +// address( +// new BeaconProxy( +// Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON, +// abi.encodeCall(OptimismOptimismSuperchainERC20.initialize, (_remoteToken, _name, _symbol, +// _decimals)) +// ) +// ) +// ); +// } + +// /// @notice Helper function to setup a mock and expect a call to it. +// function _mockAndExpect(address _receiver, bytes memory _calldata, bytes memory _returned) internal { +// vm.mockCall(_receiver, _calldata, _returned); +// vm.expectCall(_receiver, _calldata); +// } + +// /// @notice Test that the contract's `initializer` sets the correct values. +// function test_initializer_succeeds() public view { +// assertEq(optimismSuperchainERC20.name(), NAME); +// assertEq(optimismSuperchainERC20.symbol(), SYMBOL); +// assertEq(optimismSuperchainERC20.decimals(), DECIMALS); +// assertEq(optimismSuperchainERC20.remoteToken(), REMOTE_TOKEN); +// } + +// /// @notice Tests the `initialize` function reverts when the contract is already initialized. +// function testFuzz_initializer_reverts( +// address _remoteToken, +// string memory _name, +// string memory _symbol, +// uint8 _decimals +// ) +// public +// { +// // Expect the revert with `InvalidInitialization` selector +// vm.expectRevert(Initializable.InvalidInitialization.selector); + +// // Call the `initialize` function again +// optimismOptimismSuperchainERC20.initialize(_remoteToken, _name, _symbol, _decimals); +// } + +// /// @notice Tests the `mint` function reverts when the caller is not the bridge. +// function testFuzz_mint_callerNotBridge_reverts(address _caller, address _to, uint256 _amount) public { +// // Ensure the caller is not the bridge +// vm.assume(_caller != SUPERCHAIN_ERC20_BRIDGE); + +// // Expect the revert with `OnlySuperchainERC20Bridge` selector +// vm.expectRevert(ISuperchainERC20Errors.OnlySuperchainERC20Bridge.selector); + +// // Call the `mint` function with the non-bridge caller +// vm.prank(_caller); +// optimismSuperchainERC20.__superchainMint(_to, _amount); +// } + +// /// @notice Tests the `mint` function reverts when the amount is zero. +// function testFuzz_mint_zeroAddressTo_reverts(uint256 _amount) public { +// // Expect the revert with `ZeroAddress` selector +// vm.expectRevert(ISuperchainERC20Errors.ZeroAddress.selector); + +// // Call the `mint` function with the zero address +// vm.prank(SUPERCHAIN_ERC20_BRIDGE); +// optimismSuperchainERC20.__superchainMint({ _to: ZERO_ADDRESS, _amount: _amount }); +// } + +// /// @notice Tests the `mint` succeeds and emits the `Mint` event. +// function testFuzz_mint_succeeds(address _to, uint256 _amount) public { +// // Ensure `_to` is not the zero address +// vm.assume(_to != ZERO_ADDRESS); + +// // Get the total supply and balance of `_to` before the mint to compare later on the assertions +// uint256 _totalSupplyBefore = optimismSuperchainERC20.totalSupply(); +// uint256 _toBalanceBefore = optimismSuperchainERC20.balanceOf(_to); + +// // Look for the emit of the `Transfer` event +// vm.expectEmit(address(optimismSuperchainERC20)); +// emit IERC20.Transfer(ZERO_ADDRESS, _to, _amount); + +// // Look for the emit of the `Mint` event +// vm.expectEmit(address(optimismSuperchainERC20)); +// emit ISuperchainERC20Extension.Mint(_to, _amount); + +// // Call the `mint` function with the bridge caller +// vm.prank(SUPERCHAIN_ERC20_BRIDGE); +// optimismSuperchainERC20.__superchainMint(_to, _amount); + +// // Check the total supply and balance of `_to` after the mint were updated correctly +// assertEq(optimismSuperchainERC20.totalSupply(), _totalSupplyBefore + _amount); +// assertEq(optimismSuperchainERC20.balanceOf(_to), _toBalanceBefore + _amount); +// } + +// /// @notice Tests the `burn` function reverts when the caller is not the bridge. +// function testFuzz_burn_callerNotBridge_reverts(address _caller, address _from, uint256 _amount) public { +// // Ensure the caller is not the bridge +// vm.assume(_caller != SUPERCHAIN_ERC20_BRIDGE); + +// // Expect the revert with `OnlySuperchainERC20Bridge` selector +// vm.expectRevert(ISuperchainERC20Errors.OnlySuperchainERC20Bridge.selector); + +// // Call the `burn` function with the non-bridge caller +// vm.prank(_caller); +// optimismSuperchainERC20.__superchainBurn(_from, _amount); +// } + +// /// @notice Tests the `burn` function reverts when the amount is zero. +// function testFuzz_burn_zeroAddressFrom_reverts(uint256 _amount) public { +// // Expect the revert with `ZeroAddress` selector +// vm.expectRevert(ISuperchainERC20Errors.ZeroAddress.selector); + +// // Call the `burn` function with the zero address +// vm.prank(SUPERCHAIN_ERC20_BRIDGE); +// optimismSuperchainERC20.__superchainBurn({ _from: ZERO_ADDRESS, _amount: _amount }); +// } + +// /// @notice Tests the `burn` burns the amount and emits the `Burn` event. +// function testFuzz_burn_succeeds(address _from, uint256 _amount) public { +// // Ensure `_from` is not the zero address +// vm.assume(_from != ZERO_ADDRESS); + +// // Mint some tokens to `_from` so then they can be burned +// vm.prank(SUPERCHAIN_ERC20_BRIDGE); +// optimismSuperchainERC20.__superchainMint(_from, _amount); + +// // Get the total supply and balance of `_from` before the burn to compare later on the assertions +// uint256 _totalSupplyBefore = optimismSuperchainERC20.totalSupply(); +// uint256 _fromBalanceBefore = optimismSuperchainERC20.balanceOf(_from); + +// // Look for the emit of the `Transfer` event +// vm.expectEmit(address(optimismSuperchainERC20)); +// emit IERC20.Transfer(_from, ZERO_ADDRESS, _amount); + +// // Look for the emit of the `Burn` event +// vm.expectEmit(address(optimismSuperchainERC20)); +// emit ISuperchainERC20Extension.Burn(_from, _amount); + +// // Call the `burn` function with the bridge caller +// vm.prank(SUPERCHAIN_ERC20_BRIDGE); +// optimismSuperchainERC20.__superchainBurn(_from, _amount); + +// // Check the total supply and balance of `_from` after the burn were updated correctly +// assertEq(optimismSuperchainERC20.totalSupply(), _totalSupplyBefore - _amount); +// assertEq(optimismSuperchainERC20.balanceOf(_from), _fromBalanceBefore - _amount); +// } + +// /// @notice Tests the `decimals` function always returns the correct value. +// function testFuzz_decimals_succeeds(uint8 _decimals) public { +// OptimismSuperchainERC20 _newSuperchainERC20 = _deploySuperchainERC20Proxy(REMOTE_TOKEN, NAME, SYMBOL, +// _decimals); +// assertEq(_newSuperchainERC20.decimals(), _decimals); +// } + +// /// @notice Tests the `REMOTE_TOKEN` function always returns the correct value. +// function testFuzz_remoteToken_succeeds(address _remoteToken) public { +// OptimismSuperchainERC20 _newSuperchainERC20 = _deploySuperchainERC20Proxy(_remoteToken, NAME, SYMBOL, +// DECIMALS); +// assertEq(_newSuperchainERC20.remoteToken(), _remoteToken); +// } + +// /// @notice Tests the `name` function always returns the correct value. +// function testFuzz_name_succeeds(string memory _name) public { +// OptimismSuperchainERC20 _newSuperchainERC20 = _deploySuperchainERC20Proxy(REMOTE_TOKEN, _name, SYMBOL, +// DECIMALS); +// assertEq(_newSuperchainERC20.name(), _name); +// } + +// /// @notice Tests the `symbol` function always returns the correct value. +// function testFuzz_symbol_succeeds(string memory _symbol) public { +// OptimismSuperchainERC20 _newSuperchainERC20 = _deploySuperchainERC20Proxy(REMOTE_TOKEN, NAME, _symbol, +// DECIMALS); +// assertEq(_newSuperchainERC20.symbol(), _symbol); +// } + +// /// @notice Tests that the `supportsInterface` function returns true for the `ISuperchainERC20` interface. +// function test_supportInterface_succeeds() public view { +// assertTrue(optimismSuperchainERC20.supportsInterface(type(IERC165).interfaceId)); +// assertTrue(optimismSuperchainERC20.supportsInterface(type(IOptimismSuperchainERC20Extension).interfaceId)); +// } + +// /// @notice Tests that the `supportsInterface` function returns false for any other interface than the +// /// `ISuperchainERC20` one. +// function testFuzz_supportInterface_returnFalse(bytes4 _interfaceId) public view { +// vm.assume(_interfaceId != type(IERC165).interfaceId); +// vm.assume(_interfaceId != type(IOptimismSuperchainERC20Extension).interfaceId); +// assertFalse(optimismSuperchainERC20.supportsInterface(_interfaceId)); +// } +// } diff --git a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Factory.t.sol b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Factory.t.sol index c67507639eee..3636317156a7 100644 --- a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Factory.t.sol +++ b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Factory.t.sol @@ -9,7 +9,7 @@ import { Predeploys } from "src/libraries/Predeploys.sol"; import { CREATE3, Bytes32AddressLib } from "@rari-capital/solmate/src/utils/CREATE3.sol"; // Target contract -import { ISuperchainERC20 } from "src/L2/interfaces/ISuperchainERC20.sol"; +import { IOptimismSuperchainERC20 } from "src/L2/interfaces/IOptimismSuperchainERC20.sol"; import { IOptimismSuperchainERC20Factory } from "src/L2/interfaces/IOptimismSuperchainERC20Factory.sol"; /// @title OptimismSuperchainERC20FactoryTest @@ -50,10 +50,10 @@ contract OptimismSuperchainERC20FactoryTest is Bridge_Initializer { // Assert assertTrue(addr == deployment); - assertTrue(ISuperchainERC20(deployment).decimals() == _decimals); - assertTrue(ISuperchainERC20(deployment).remoteToken() == _remoteToken); - assertEq(ISuperchainERC20(deployment).name(), _name); - assertEq(ISuperchainERC20(deployment).symbol(), _symbol); + assertTrue(IOptimismSuperchainERC20(deployment).decimals() == _decimals); + assertTrue(IOptimismSuperchainERC20(deployment).remoteToken() == _remoteToken); + assertEq(IOptimismSuperchainERC20(deployment).name(), _name); + assertEq(IOptimismSuperchainERC20(deployment).symbol(), _symbol); assertEq(l2OptimismSuperchainERC20Factory.deployments(deployment), _remoteToken); } diff --git a/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol index c4f02be6ad57..10a65f66d2f4 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol @@ -19,67 +19,28 @@ import { BeaconProxy } from "@openzeppelin/contracts-v5/proxy/beacon/BeaconProxy import { SuperchainERC20, ISuperchainERC20Extension } from "src/L2/SuperchainERC20.sol"; import { ISuperchainERC20Errors } from "src/L2/interfaces/ISuperchainERC20.sol"; +contract SuperchainERC20Implementation is SuperchainERC20 { + function name() public pure override returns (string memory) { + return "SuperchainERC20"; + } + + function symbol() public pure override returns (string memory) { + return "SCE"; + } +} + /// @title SuperchainERC20Test /// @notice Contract for testing the SuperchainERC20 contract. contract SuperchainERC20Test is Test { address internal constant ZERO_ADDRESS = address(0); - address internal constant REMOTE_TOKEN = address(0x123); - string internal constant NAME = "SuperchainERC20"; - string internal constant SYMBOL = "SCE"; - uint8 internal constant DECIMALS = 18; address internal constant SUPERCHAIN_ERC20_BRIDGE = Predeploys.SUPERCHAIN_ERC20_BRIDGE; address internal constant MESSENGER = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER; - SuperchainERC20 public superchainERC20Impl; SuperchainERC20 public superchainERC20; /// @notice Sets up the test suite. function setUp() public { - superchainERC20Impl = new SuperchainERC20(); - - // 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 SuperchainERC20 contract. - function _deploySuperchainERC20Proxy( - address _remoteToken, - string memory _name, - string memory _symbol, - uint8 _decimals - ) - internal - returns (SuperchainERC20) - { - return SuperchainERC20( - address( - new BeaconProxy( - Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON, - abi.encodeCall(SuperchainERC20.initialize, (_remoteToken, _name, _symbol, _decimals)) - ) - ) - ); + superchainERC20 = new SuperchainERC20Implementation(); } /// @notice Helper function to setup a mock and expect a call to it. @@ -88,32 +49,8 @@ contract SuperchainERC20Test is Test { vm.expectCall(_receiver, _calldata); } - /// @notice Test that the contract's `initializer` sets the correct values. - function test_initializer_succeeds() public view { - assertEq(superchainERC20.name(), NAME); - assertEq(superchainERC20.symbol(), SYMBOL); - assertEq(superchainERC20.decimals(), DECIMALS); - assertEq(superchainERC20.remoteToken(), REMOTE_TOKEN); - } - - /// @notice Tests the `initialize` function reverts when the contract is already initialized. - function testFuzz_initializer_reverts( - address _remoteToken, - string memory _name, - string memory _symbol, - uint8 _decimals - ) - public - { - // Expect the revert with `InvalidInitialization` selector - vm.expectRevert(Initializable.InvalidInitialization.selector); - - // Call the `initialize` function again - superchainERC20.initialize(_remoteToken, _name, _symbol, _decimals); - } - /// @notice Tests the `mint` function reverts when the caller is not the bridge. - function testFuzz_mint_callerNotBridge_reverts(address _caller, address _to, uint256 _amount) public { + function testFuzz___superchainMint_callerNotBridge_reverts(address _caller, address _to, uint256 _amount) public { // Ensure the caller is not the bridge vm.assume(_caller != SUPERCHAIN_ERC20_BRIDGE); @@ -126,7 +63,7 @@ contract SuperchainERC20Test is Test { } /// @notice Tests the `mint` function reverts when the amount is zero. - function testFuzz_mint_zeroAddressTo_reverts(uint256 _amount) public { + function testFuzz___superchainMint_zeroAddressTo_reverts(uint256 _amount) public { // Expect the revert with `ZeroAddress` selector vm.expectRevert(ISuperchainERC20Errors.ZeroAddress.selector); @@ -136,7 +73,7 @@ contract SuperchainERC20Test is Test { } /// @notice Tests the `mint` succeeds and emits the `Mint` event. - function testFuzz_mint_succeeds(address _to, uint256 _amount) public { + function testFuzz___superchainMint_succeeds(address _to, uint256 _amount) public { // Ensure `_to` is not the zero address vm.assume(_to != ZERO_ADDRESS); @@ -162,7 +99,13 @@ contract SuperchainERC20Test is Test { } /// @notice Tests the `burn` function reverts when the caller is not the bridge. - function testFuzz_burn_callerNotBridge_reverts(address _caller, address _from, uint256 _amount) public { + function testFuzz___superchainBurn_callerNotBridge_reverts( + address _caller, + address _from, + uint256 _amount + ) + public + { // Ensure the caller is not the bridge vm.assume(_caller != SUPERCHAIN_ERC20_BRIDGE); @@ -175,7 +118,7 @@ contract SuperchainERC20Test is Test { } /// @notice Tests the `burn` function reverts when the amount is zero. - function testFuzz_burn_zeroAddressFrom_reverts(uint256 _amount) public { + function testFuzz___superchainBurn_zeroAddressFrom_reverts(uint256 _amount) public { // Expect the revert with `ZeroAddress` selector vm.expectRevert(ISuperchainERC20Errors.ZeroAddress.selector); @@ -185,7 +128,7 @@ contract SuperchainERC20Test is Test { } /// @notice Tests the `burn` burns the amount and emits the `Burn` event. - function testFuzz_burn_succeeds(address _from, uint256 _amount) public { + function testFuzz___superchainBurn_succeeds(address _from, uint256 _amount) public { // Ensure `_from` is not the zero address vm.assume(_from != ZERO_ADDRESS); @@ -213,42 +156,4 @@ contract SuperchainERC20Test is Test { assertEq(superchainERC20.totalSupply(), _totalSupplyBefore - _amount); assertEq(superchainERC20.balanceOf(_from), _fromBalanceBefore - _amount); } - - /// @notice Tests the `decimals` function always returns the correct value. - function testFuzz_decimals_succeeds(uint8 _decimals) public { - SuperchainERC20 _newSuperchainERC20 = _deploySuperchainERC20Proxy(REMOTE_TOKEN, NAME, SYMBOL, _decimals); - assertEq(_newSuperchainERC20.decimals(), _decimals); - } - - /// @notice Tests the `REMOTE_TOKEN` function always returns the correct value. - function testFuzz_remoteToken_succeeds(address _remoteToken) public { - SuperchainERC20 _newSuperchainERC20 = _deploySuperchainERC20Proxy(_remoteToken, NAME, SYMBOL, DECIMALS); - assertEq(_newSuperchainERC20.remoteToken(), _remoteToken); - } - - /// @notice Tests the `name` function always returns the correct value. - function testFuzz_name_succeeds(string memory _name) public { - SuperchainERC20 _newSuperchainERC20 = _deploySuperchainERC20Proxy(REMOTE_TOKEN, _name, SYMBOL, DECIMALS); - assertEq(_newSuperchainERC20.name(), _name); - } - - /// @notice Tests the `symbol` function always returns the correct value. - function testFuzz_symbol_succeeds(string memory _symbol) public { - SuperchainERC20 _newSuperchainERC20 = _deploySuperchainERC20Proxy(REMOTE_TOKEN, NAME, _symbol, DECIMALS); - assertEq(_newSuperchainERC20.symbol(), _symbol); - } - - /// @notice Tests that the `supportsInterface` function returns true for the `ISuperchainERC20` interface. - function test_supportInterface_succeeds() public view { - assertTrue(superchainERC20.supportsInterface(type(IERC165).interfaceId)); - assertTrue(superchainERC20.supportsInterface(type(ISuperchainERC20Extension).interfaceId)); - } - - /// @notice Tests that the `supportsInterface` function returns false for any other interface than the - /// `ISuperchainERC20` one. - function testFuzz_supportInterface_returnFalse(bytes4 _interfaceId) public view { - vm.assume(_interfaceId != type(IERC165).interfaceId); - vm.assume(_interfaceId != type(ISuperchainERC20Extension).interfaceId); - assertFalse(superchainERC20.supportsInterface(_interfaceId)); - } } diff --git a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.unguided.t.sol b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.unguided.t.sol index 780cb2fb7788..db7c2243a1cd 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.unguided.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.unguided.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.25; import { ProtocolHandler } from "../handlers/Protocol.t.sol"; import { EnumerableMap } from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; -import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; +import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; import { CompatibleAssert } from "../helpers/CompatibleAssert.t.sol"; // TODO: add fuzz_sendERC20 when we implement non-atomic bridging @@ -15,9 +15,9 @@ contract ProtocolUnguided is ProtocolHandler, CompatibleAssert { function fuzz_mint(uint256 tokenIndex, address to, address sender, uint256 amount) external { address token = allSuperTokens[bound(tokenIndex, 0, allSuperTokens.length)]; bytes32 salt = MESSENGER.superTokenInitDeploySalts(token); - amount = bound(amount, 0, type(uint256).max - SuperchainERC20(token).totalSupply()); + amount = bound(amount, 0, type(uint256).max - OptimismSuperchainERC20(token).totalSupply()); vm.prank(sender); - try SuperchainERC20(token).__superchainMint(to, amount) { + try OptimismSuperchainERC20(token).__superchainMint(to, amount) { compatibleAssert(sender == BRIDGE); (, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(salt); ghost_totalSupplyAcrossChains.set(salt, currentValue + amount); @@ -31,9 +31,9 @@ contract ProtocolUnguided is ProtocolHandler, CompatibleAssert { function fuzz_burn(uint256 tokenIndex, address from, address sender, uint256 amount) external { address token = allSuperTokens[bound(tokenIndex, 0, allSuperTokens.length)]; bytes32 salt = MESSENGER.superTokenInitDeploySalts(token); - uint256 senderBalance = SuperchainERC20(token).balanceOf(sender); + uint256 senderBalance = OptimismSuperchainERC20(token).balanceOf(sender); vm.prank(sender); - try SuperchainERC20(token).__superchainBurn(from, amount) { + try OptimismSuperchainERC20(token).__superchainBurn(from, amount) { compatibleAssert(sender == BRIDGE); (, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(salt); ghost_totalSupplyAcrossChains.set(salt, currentValue - amount); @@ -56,7 +56,7 @@ contract ProtocolUnguided is ProtocolHandler, CompatibleAssert { { vm.prank(sender); // revert is possible in bound, but is not part of the external call - try SuperchainERC20(allSuperTokens[bound(tokenIndex, 0, allSuperTokens.length)]).initialize( + try OptimismSuperchainERC20(allSuperTokens[bound(tokenIndex, 0, allSuperTokens.length)]).initialize( remoteToken, name, symbol, decimals ) { compatibleAssert(false); diff --git a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/handlers/Protocol.t.sol b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/handlers/Protocol.t.sol index 554172a5c15b..9dbd5d5fc4a8 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/handlers/Protocol.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/handlers/Protocol.t.sol @@ -6,7 +6,7 @@ import { StdUtils } from "forge-std/StdUtils.sol"; import { ERC1967Proxy } from "@openzeppelin/contracts-v5/proxy/ERC1967/ERC1967Proxy.sol"; import { EnumerableMap } from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; -import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; +import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; import { OptimismSuperchainERC20ForToBProperties } from "../helpers/OptimismSuperchainERC20ForToBProperties.t.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; import { MockL2ToL2CrossDomainMessenger } from "../helpers/MockL2ToL2CrossDomainMessenger.t.sol"; @@ -22,7 +22,7 @@ contract ProtocolHandler is TestBase, StdUtils, Actors { address internal constant BRIDGE = Predeploys.L2_STANDARD_BRIDGE; MockL2ToL2CrossDomainMessenger internal constant MESSENGER = MockL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); - SuperchainERC20 internal superchainERC20Impl; + OptimismSuperchainERC20 internal superchainERC20Impl; // NOTE: having more options for this enables the fuzzer to configure // different supertokens for the same remote token string[] internal WORDS = ["TOKENS"]; @@ -79,7 +79,7 @@ contract ProtocolHandler is TestBase, StdUtils, Actors { index = bound(index, 0, allSuperTokens.length - 1); address addr = allSuperTokens[index]; vm.prank(BRIDGE); - SuperchainERC20(addr).__superchainMint(currentActor(), amount); + OptimismSuperchainERC20(addr).__superchainMint(currentActor(), amount); // currentValue will be zero if key is not present (, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(MESSENGER.superTokenInitDeploySalts(addr)); ghost_totalSupplyAcrossChains.set(MESSENGER.superTokenInitDeploySalts(addr), currentValue + amount); @@ -97,7 +97,7 @@ contract ProtocolHandler is TestBase, StdUtils, Actors { withActor(msg.sender) { vm.prank(currentActor()); - SuperchainERC20(allSuperTokens[bound(tokenIndex, 0, allSuperTokens.length)]).transfer( + OptimismSuperchainERC20(allSuperTokens[bound(tokenIndex, 0, allSuperTokens.length)]).transfer( getActorByRawIndex(toIndex), amount ); } @@ -112,7 +112,7 @@ contract ProtocolHandler is TestBase, StdUtils, Actors { withActor(msg.sender) { vm.prank(currentActor()); - SuperchainERC20(allSuperTokens[bound(tokenIndex, 0, allSuperTokens.length)]).transferFrom( + OptimismSuperchainERC20(allSuperTokens[bound(tokenIndex, 0, allSuperTokens.length)]).transferFrom( getActorByRawIndex(fromIndex), getActorByRawIndex(toIndex), amount ); } @@ -126,7 +126,7 @@ contract ProtocolHandler is TestBase, StdUtils, Actors { withActor(msg.sender) { vm.prank(currentActor()); - SuperchainERC20(allSuperTokens[bound(tokenIndex, 0, allSuperTokens.length)]).approve( + OptimismSuperchainERC20(allSuperTokens[bound(tokenIndex, 0, allSuperTokens.length)]).approve( getActorByRawIndex(spenderIndex), amount ); } @@ -151,7 +151,7 @@ contract ProtocolHandler is TestBase, StdUtils, Actors { uint256 chainId ) internal - returns (SuperchainERC20 supertoken) + returns (OptimismSuperchainERC20 supertoken) { // this salt would be used in production. Tokens sharing it will be bridgable with each other bytes32 realSalt = keccak256(abi.encode(remoteToken, name, symbol, decimals)); @@ -162,12 +162,12 @@ contract ProtocolHandler is TestBase, StdUtils, Actors { // what we use in the tests to walk around two contracts needing two different addresses // tbf we could be using CREATE1, but this feels more verbose bytes32 hackySalt = keccak256(abi.encode(remoteToken, name, symbol, decimals, chainId)); - supertoken = SuperchainERC20( + supertoken = OptimismSuperchainERC20( address( - // TODO: Use the SuperchainERC20 Beacon Proxy + // TODO: Use the OptimismSuperchainERC20 Beacon Proxy new ERC1967Proxy{ salt: hackySalt }( address(superchainERC20Impl), - abi.encodeCall(SuperchainERC20.initialize, (remoteToken, name, symbol, decimals)) + abi.encodeCall(OptimismSuperchainERC20.initialize, (remoteToken, name, symbol, decimals)) ) ) ); diff --git a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/helpers/OptimismSuperchainERC20ForToBProperties.t.sol b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/helpers/OptimismSuperchainERC20ForToBProperties.t.sol index 7342fc0b1d20..9f80cda92fcc 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/helpers/OptimismSuperchainERC20ForToBProperties.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/helpers/OptimismSuperchainERC20ForToBProperties.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: AGPL-3 pragma solidity ^0.8.25; -import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; +import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; -contract OptimismSuperchainERC20ForToBProperties is SuperchainERC20 { +contract OptimismSuperchainERC20ForToBProperties is OptimismSuperchainERC20 { /// @notice This is used by CryticERC20ExternalBasicProperties (only used /// in Medusa testing campaign)to know which properties to test, and /// remains here so Medusa and Foundry test campaigns can use a single diff --git a/packages/contracts-bedrock/test/vendor/InitializableOZv5.t.sol b/packages/contracts-bedrock/test/vendor/InitializableOZv5.t.sol index e23d50927229..0820f987414a 100644 --- a/packages/contracts-bedrock/test/vendor/InitializableOZv5.t.sol +++ b/packages/contracts-bedrock/test/vendor/InitializableOZv5.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.25; import { Test } from "forge-std/Test.sol"; -import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; +import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; import { Initializable } from "@openzeppelin/contracts-v5/proxy/utils/Initializable.sol"; /// @title InitializerOZv5_Test @@ -28,11 +28,11 @@ contract InitializerOZv5_Test is Test { // Initialize the `contracts` array with the addresses of the contracts to test and the // calldata used to initialize them - // SuperchainERC20 + // OptimismSuperchainERC20 contracts.push( InitializeableContract({ - target: address(new SuperchainERC20()), - initCalldata: abi.encodeCall(SuperchainERC20.initialize, (address(0), "", "", 18)) + target: address(new OptimismSuperchainERC20()), + initCalldata: abi.encodeCall(OptimismSuperchainERC20.initialize, (address(0), "", "", 18)) }) ); } From ba100f826e1dafe7ddf58e07fc257683ac8cb084 Mon Sep 17 00:00:00 2001 From: 0xDiscotech <131301107+0xDiscotech@users.noreply.github.com> Date: Tue, 1 Oct 2024 15:16:19 -0300 Subject: [PATCH 05/10] chore: update superchain erc20 events --- .../src/L2/SuperchainERC20.sol | 4 +- .../interfaces/IOptimismSuperchainERC20.sol | 10 + .../src/L2/interfaces/ISuperchainERC20.sol | 8 +- .../test/L2/OptimismSuperchainERC20.t.sol | 517 +++++++++--------- .../test/L2/SuperchainERC20.t.sol | 6 +- 5 files changed, 275 insertions(+), 270 deletions(-) diff --git a/packages/contracts-bedrock/src/L2/SuperchainERC20.sol b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol index feb4873b1546..9661e3406bde 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol @@ -34,7 +34,7 @@ abstract contract SuperchainERC20 is ERC20, ISuperchainERC20Extension, ISemver { _mint(_to, _amount); - emit Mint(_to, _amount); + emit SuperchainMint(_to, _amount); } /// @notice Allows the SuperchainERC20Bridge to burn tokens. @@ -45,6 +45,6 @@ abstract contract SuperchainERC20 is ERC20, ISuperchainERC20Extension, ISemver { _burn(_from, _amount); - emit Burn(_from, _amount); + emit SuperchainBurn(_from, _amount); } } diff --git a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol index 8f2111ba9d0d..5fe204b9236c 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol @@ -15,6 +15,16 @@ interface IOptimismSuperchainERC20Errors { /// @title ISuperchainERC20Extension /// @notice This interface is available on the SuperchainERC20 contract. interface IOptimismSuperchainERC20Extension is ISuperchainERC20Extension, IOptimismSuperchainERC20Errors { + /// @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 Allows the L2StandardBridge and SuperchainERC20Bridge to mint tokens. /// @param _to Address to mint tokens to. /// @param _amount Amount of tokens to mint. diff --git a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol index 149fde37a03a..9ae84f6e7d2f 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol @@ -17,15 +17,15 @@ interface ISuperchainERC20Errors { /// @title ISuperchainERC20Extension /// @notice This interface is available on the SuperchainERC20 contract. interface ISuperchainERC20Extension is ISuperchainERC20Errors { - /// @notice Emitted whenever tokens are minted for an account. + /// @notice Emitted whenever tokens are minted for by the SuperchainERC20Bridge. /// @param account Address of the account tokens are being minted for. /// @param amount Amount of tokens minted. - event Mint(address indexed account, uint256 amount); + event SuperchainMint(address indexed account, uint256 amount); - /// @notice Emitted whenever tokens are burned from an account. + /// @notice Emitted whenever tokens are burned by the SuperchainERC20Bridge. /// @param account Address of the account tokens are being burned from. /// @param amount Amount of tokens burned. - event Burn(address indexed account, uint256 amount); + event SuperchainBurn(address indexed account, uint256 amount); /// @notice Allows the SuperchainERC20Bridge to mint tokens. /// @param _to Address to mint tokens to. diff --git a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol index 70cce6f907a0..630eb5214f78 100644 --- a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol @@ -1,261 +1,256 @@ -// // 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 { IERC20 } from "@openzeppelin/contracts-v5/token/ERC20/IERC20.sol"; -// import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; -// 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 { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; -// import { ISuperchainERC20Errors } from "src/L2/interfaces/ISuperchainERC20.sol"; - -// /// @title OptimismSuperchainERC20Test -// /// @notice Contract for testing the OptimismSuperchainERC20 contract. -// contract OptimismSuperchainERC20Test is Test { -// address internal constant ZERO_ADDRESS = address(0); -// address internal constant REMOTE_TOKEN = address(0x123); -// string internal constant NAME = "OptimismSuperchainERC20"; -// string internal constant SYMBOL = "OSC"; -// uint8 internal constant DECIMALS = 18; -// address internal constant L2_BRIDGE = Predeploys.L2_STANDARD_BRIDGE; -// address internal constant MESSENGER = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER; - -// OptimismSuperchainERC20 public optimismSuperchainERC20Impl; -// OptimismSuperchainERC20 public optimismSuperchainERC20; - -// /// @notice Sets up the test suite. -// function setUp() public { -// optimismSuperchainERC20Impl = new OptimismSuperchainERC20(); - -// // Deploy the OptimismSuperchainERC20Beacon contract -// _deployBeacon(); - -// optimismSuperchainERC20 = _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(optimismSuperchainERC20Impl)) -// ); -// } - -// /// @notice Helper function to deploy a proxy of the OptimismSuperchainERC20 contract. -// function _deploySuperchainERC20Proxy( -// address _remoteToken, -// string memory _name, -// string memory _symbol, -// uint8 _decimals -// ) -// internal -// returns (OptimismSuperchainERC20) -// { -// return OptimismSuperchainERC20( -// address( -// new BeaconProxy( -// Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON, -// abi.encodeCall(OptimismOptimismSuperchainERC20.initialize, (_remoteToken, _name, _symbol, -// _decimals)) -// ) -// ) -// ); -// } - -// /// @notice Helper function to setup a mock and expect a call to it. -// function _mockAndExpect(address _receiver, bytes memory _calldata, bytes memory _returned) internal { -// vm.mockCall(_receiver, _calldata, _returned); -// vm.expectCall(_receiver, _calldata); -// } - -// /// @notice Test that the contract's `initializer` sets the correct values. -// function test_initializer_succeeds() public view { -// assertEq(optimismSuperchainERC20.name(), NAME); -// assertEq(optimismSuperchainERC20.symbol(), SYMBOL); -// assertEq(optimismSuperchainERC20.decimals(), DECIMALS); -// assertEq(optimismSuperchainERC20.remoteToken(), REMOTE_TOKEN); -// } - -// /// @notice Tests the `initialize` function reverts when the contract is already initialized. -// function testFuzz_initializer_reverts( -// address _remoteToken, -// string memory _name, -// string memory _symbol, -// uint8 _decimals -// ) -// public -// { -// // Expect the revert with `InvalidInitialization` selector -// vm.expectRevert(Initializable.InvalidInitialization.selector); - -// // Call the `initialize` function again -// optimismOptimismSuperchainERC20.initialize(_remoteToken, _name, _symbol, _decimals); -// } - -// /// @notice Tests the `mint` function reverts when the caller is not the bridge. -// function testFuzz_mint_callerNotBridge_reverts(address _caller, address _to, uint256 _amount) public { -// // Ensure the caller is not the bridge -// vm.assume(_caller != SUPERCHAIN_ERC20_BRIDGE); - -// // Expect the revert with `OnlySuperchainERC20Bridge` selector -// vm.expectRevert(ISuperchainERC20Errors.OnlySuperchainERC20Bridge.selector); - -// // Call the `mint` function with the non-bridge caller -// vm.prank(_caller); -// optimismSuperchainERC20.__superchainMint(_to, _amount); -// } - -// /// @notice Tests the `mint` function reverts when the amount is zero. -// function testFuzz_mint_zeroAddressTo_reverts(uint256 _amount) public { -// // Expect the revert with `ZeroAddress` selector -// vm.expectRevert(ISuperchainERC20Errors.ZeroAddress.selector); - -// // Call the `mint` function with the zero address -// vm.prank(SUPERCHAIN_ERC20_BRIDGE); -// optimismSuperchainERC20.__superchainMint({ _to: ZERO_ADDRESS, _amount: _amount }); -// } - -// /// @notice Tests the `mint` succeeds and emits the `Mint` event. -// function testFuzz_mint_succeeds(address _to, uint256 _amount) public { -// // Ensure `_to` is not the zero address -// vm.assume(_to != ZERO_ADDRESS); - -// // Get the total supply and balance of `_to` before the mint to compare later on the assertions -// uint256 _totalSupplyBefore = optimismSuperchainERC20.totalSupply(); -// uint256 _toBalanceBefore = optimismSuperchainERC20.balanceOf(_to); - -// // Look for the emit of the `Transfer` event -// vm.expectEmit(address(optimismSuperchainERC20)); -// emit IERC20.Transfer(ZERO_ADDRESS, _to, _amount); - -// // Look for the emit of the `Mint` event -// vm.expectEmit(address(optimismSuperchainERC20)); -// emit ISuperchainERC20Extension.Mint(_to, _amount); - -// // Call the `mint` function with the bridge caller -// vm.prank(SUPERCHAIN_ERC20_BRIDGE); -// optimismSuperchainERC20.__superchainMint(_to, _amount); - -// // Check the total supply and balance of `_to` after the mint were updated correctly -// assertEq(optimismSuperchainERC20.totalSupply(), _totalSupplyBefore + _amount); -// assertEq(optimismSuperchainERC20.balanceOf(_to), _toBalanceBefore + _amount); -// } - -// /// @notice Tests the `burn` function reverts when the caller is not the bridge. -// function testFuzz_burn_callerNotBridge_reverts(address _caller, address _from, uint256 _amount) public { -// // Ensure the caller is not the bridge -// vm.assume(_caller != SUPERCHAIN_ERC20_BRIDGE); - -// // Expect the revert with `OnlySuperchainERC20Bridge` selector -// vm.expectRevert(ISuperchainERC20Errors.OnlySuperchainERC20Bridge.selector); - -// // Call the `burn` function with the non-bridge caller -// vm.prank(_caller); -// optimismSuperchainERC20.__superchainBurn(_from, _amount); -// } - -// /// @notice Tests the `burn` function reverts when the amount is zero. -// function testFuzz_burn_zeroAddressFrom_reverts(uint256 _amount) public { -// // Expect the revert with `ZeroAddress` selector -// vm.expectRevert(ISuperchainERC20Errors.ZeroAddress.selector); - -// // Call the `burn` function with the zero address -// vm.prank(SUPERCHAIN_ERC20_BRIDGE); -// optimismSuperchainERC20.__superchainBurn({ _from: ZERO_ADDRESS, _amount: _amount }); -// } - -// /// @notice Tests the `burn` burns the amount and emits the `Burn` event. -// function testFuzz_burn_succeeds(address _from, uint256 _amount) public { -// // Ensure `_from` is not the zero address -// vm.assume(_from != ZERO_ADDRESS); - -// // Mint some tokens to `_from` so then they can be burned -// vm.prank(SUPERCHAIN_ERC20_BRIDGE); -// optimismSuperchainERC20.__superchainMint(_from, _amount); - -// // Get the total supply and balance of `_from` before the burn to compare later on the assertions -// uint256 _totalSupplyBefore = optimismSuperchainERC20.totalSupply(); -// uint256 _fromBalanceBefore = optimismSuperchainERC20.balanceOf(_from); - -// // Look for the emit of the `Transfer` event -// vm.expectEmit(address(optimismSuperchainERC20)); -// emit IERC20.Transfer(_from, ZERO_ADDRESS, _amount); - -// // Look for the emit of the `Burn` event -// vm.expectEmit(address(optimismSuperchainERC20)); -// emit ISuperchainERC20Extension.Burn(_from, _amount); - -// // Call the `burn` function with the bridge caller -// vm.prank(SUPERCHAIN_ERC20_BRIDGE); -// optimismSuperchainERC20.__superchainBurn(_from, _amount); - -// // Check the total supply and balance of `_from` after the burn were updated correctly -// assertEq(optimismSuperchainERC20.totalSupply(), _totalSupplyBefore - _amount); -// assertEq(optimismSuperchainERC20.balanceOf(_from), _fromBalanceBefore - _amount); -// } - -// /// @notice Tests the `decimals` function always returns the correct value. -// function testFuzz_decimals_succeeds(uint8 _decimals) public { -// OptimismSuperchainERC20 _newSuperchainERC20 = _deploySuperchainERC20Proxy(REMOTE_TOKEN, NAME, SYMBOL, -// _decimals); -// assertEq(_newSuperchainERC20.decimals(), _decimals); -// } - -// /// @notice Tests the `REMOTE_TOKEN` function always returns the correct value. -// function testFuzz_remoteToken_succeeds(address _remoteToken) public { -// OptimismSuperchainERC20 _newSuperchainERC20 = _deploySuperchainERC20Proxy(_remoteToken, NAME, SYMBOL, -// DECIMALS); -// assertEq(_newSuperchainERC20.remoteToken(), _remoteToken); -// } - -// /// @notice Tests the `name` function always returns the correct value. -// function testFuzz_name_succeeds(string memory _name) public { -// OptimismSuperchainERC20 _newSuperchainERC20 = _deploySuperchainERC20Proxy(REMOTE_TOKEN, _name, SYMBOL, -// DECIMALS); -// assertEq(_newSuperchainERC20.name(), _name); -// } - -// /// @notice Tests the `symbol` function always returns the correct value. -// function testFuzz_symbol_succeeds(string memory _symbol) public { -// OptimismSuperchainERC20 _newSuperchainERC20 = _deploySuperchainERC20Proxy(REMOTE_TOKEN, NAME, _symbol, -// DECIMALS); -// assertEq(_newSuperchainERC20.symbol(), _symbol); -// } - -// /// @notice Tests that the `supportsInterface` function returns true for the `ISuperchainERC20` interface. -// function test_supportInterface_succeeds() public view { -// assertTrue(optimismSuperchainERC20.supportsInterface(type(IERC165).interfaceId)); -// assertTrue(optimismSuperchainERC20.supportsInterface(type(IOptimismSuperchainERC20Extension).interfaceId)); -// } - -// /// @notice Tests that the `supportsInterface` function returns false for any other interface than the -// /// `ISuperchainERC20` one. -// function testFuzz_supportInterface_returnFalse(bytes4 _interfaceId) public view { -// vm.assume(_interfaceId != type(IERC165).interfaceId); -// vm.assume(_interfaceId != type(IOptimismSuperchainERC20Extension).interfaceId); -// assertFalse(optimismSuperchainERC20.supportsInterface(_interfaceId)); -// } -// } +// 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 { IERC20 } from "@openzeppelin/contracts-v5/token/ERC20/IERC20.sol"; +import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; +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 { OptimismSuperchainERC20, IOptimismSuperchainERC20Extension } from "src/L2/OptimismSuperchainERC20.sol"; +import { ISuperchainERC20Errors } from "src/L2/interfaces/ISuperchainERC20.sol"; + +/// @title OptimismSuperchainERC20Test +/// @notice Contract for testing the OptimismSuperchainERC20 contract. +contract OptimismSuperchainERC20Test is Test { + address internal constant ZERO_ADDRESS = address(0); + address internal constant REMOTE_TOKEN = address(0x123); + string internal constant NAME = "OptimismSuperchainERC20"; + string internal constant SYMBOL = "OSC"; + uint8 internal constant DECIMALS = 18; + address internal constant L2_BRIDGE = Predeploys.L2_STANDARD_BRIDGE; + address internal constant MESSENGER = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER; + + OptimismSuperchainERC20 public optimismSuperchainERC20Impl; + OptimismSuperchainERC20 public optimismSuperchainERC20; + + /// @notice Sets up the test suite. + function setUp() public { + optimismSuperchainERC20Impl = new OptimismSuperchainERC20(); + + // Deploy the OptimismSuperchainERC20Beacon contract + _deployBeacon(); + + optimismSuperchainERC20 = _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(optimismSuperchainERC20Impl)) + ); + } + + /// @notice Helper function to deploy a proxy of the OptimismSuperchainERC20 contract. + function _deploySuperchainERC20Proxy( + address _remoteToken, + string memory _name, + string memory _symbol, + uint8 _decimals + ) + internal + returns (OptimismSuperchainERC20) + { + return OptimismSuperchainERC20( + address( + new BeaconProxy( + Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON, + abi.encodeCall(OptimismSuperchainERC20.initialize, (_remoteToken, _name, _symbol, _decimals)) + ) + ) + ); + } + + /// @notice Helper function to setup a mock and expect a call to it. + function _mockAndExpect(address _receiver, bytes memory _calldata, bytes memory _returned) internal { + vm.mockCall(_receiver, _calldata, _returned); + vm.expectCall(_receiver, _calldata); + } + + /// @notice Test that the contract's `initializer` sets the correct values. + function test_initializer_succeeds() public view { + assertEq(optimismSuperchainERC20.name(), NAME); + assertEq(optimismSuperchainERC20.symbol(), SYMBOL); + assertEq(optimismSuperchainERC20.decimals(), DECIMALS); + assertEq(optimismSuperchainERC20.remoteToken(), REMOTE_TOKEN); + } + + /// @notice Tests the `initialize` function reverts when the contract is already initialized. + function testFuzz_initializer_reverts( + address _remoteToken, + string memory _name, + string memory _symbol, + uint8 _decimals + ) + public + { + // Expect the revert with `InvalidInitialization` selector + vm.expectRevert(Initializable.InvalidInitialization.selector); + + // Call the `initialize` function again + optimismSuperchainERC20.initialize(_remoteToken, _name, _symbol, _decimals); + } + + /// @notice Tests the `mint` function reverts when the caller is not the bridge. + function testFuzz_mint_callerNotBridge_reverts(address _caller, address _to, uint256 _amount) public { + // Ensure the caller is not the bridge + vm.assume(_caller != L2_BRIDGE); + + // Expect the revert with `OnlySuperchainERC20Bridge` selector + vm.expectRevert(ISuperchainERC20Errors.OnlySuperchainERC20Bridge.selector); + + // Call the `mint` function with the non-bridge caller + vm.prank(_caller); + optimismSuperchainERC20.__superchainMint(_to, _amount); + } + + /// @notice Tests the `mint` function reverts when the amount is zero. + function testFuzz_mint_zeroAddressTo_reverts(uint256 _amount) public { + // Expect the revert with `ZeroAddress` selector + vm.expectRevert(ISuperchainERC20Errors.ZeroAddress.selector); + + // Call the `mint` function with the zero address + vm.prank(L2_BRIDGE); + optimismSuperchainERC20.__superchainMint({ _to: ZERO_ADDRESS, _amount: _amount }); + } + + /// @notice Tests the `mint` succeeds and emits the `Mint` event. + function testFuzz_mint_succeeds(address _to, uint256 _amount) public { + // Ensure `_to` is not the zero address + vm.assume(_to != ZERO_ADDRESS); + + // Get the total supply and balance of `_to` before the mint to compare later on the assertions + uint256 _totalSupplyBefore = optimismSuperchainERC20.totalSupply(); + uint256 _toBalanceBefore = optimismSuperchainERC20.balanceOf(_to); + + // Look for the emit of the `Transfer` event + vm.expectEmit(address(optimismSuperchainERC20)); + emit IERC20.Transfer(ZERO_ADDRESS, _to, _amount); + + // Look for the emit of the `Mint` event + vm.expectEmit(address(optimismSuperchainERC20)); + emit IOptimismSuperchainERC20Extension.Mint(_to, _amount); + + // Call the `mint` function with the bridge caller + vm.prank(L2_BRIDGE); + optimismSuperchainERC20.__superchainMint(_to, _amount); + + // Check the total supply and balance of `_to` after the mint were updated correctly + assertEq(optimismSuperchainERC20.totalSupply(), _totalSupplyBefore + _amount); + assertEq(optimismSuperchainERC20.balanceOf(_to), _toBalanceBefore + _amount); + } + + /// @notice Tests the `burn` function reverts when the caller is not the bridge. + function testFuzz_burn_callerNotBridge_reverts(address _caller, address _from, uint256 _amount) public { + // Ensure the caller is not the bridge + vm.assume(_caller != L2_BRIDGE); + + // Expect the revert with `OnlySuperchainERC20Bridge` selector + vm.expectRevert(ISuperchainERC20Errors.OnlySuperchainERC20Bridge.selector); + + // Call the `burn` function with the non-bridge caller + vm.prank(_caller); + optimismSuperchainERC20.__superchainBurn(_from, _amount); + } + + /// @notice Tests the `burn` function reverts when the amount is zero. + function testFuzz_burn_zeroAddressFrom_reverts(uint256 _amount) public { + // Expect the revert with `ZeroAddress` selector + vm.expectRevert(ISuperchainERC20Errors.ZeroAddress.selector); + + // Call the `burn` function with the zero address + vm.prank(L2_BRIDGE); + optimismSuperchainERC20.__superchainBurn({ _from: ZERO_ADDRESS, _amount: _amount }); + } + + /// @notice Tests the `burn` burns the amount and emits the `Burn` event. + function testFuzz_burn_succeeds(address _from, uint256 _amount) public { + // Ensure `_from` is not the zero address + vm.assume(_from != ZERO_ADDRESS); + + // Mint some tokens to `_from` so then they can be burned + vm.prank(L2_BRIDGE); + optimismSuperchainERC20.__superchainMint(_from, _amount); + + // Get the total supply and balance of `_from` before the burn to compare later on the assertions + uint256 _totalSupplyBefore = optimismSuperchainERC20.totalSupply(); + uint256 _fromBalanceBefore = optimismSuperchainERC20.balanceOf(_from); + + // Look for the emit of the `Transfer` event + vm.expectEmit(address(optimismSuperchainERC20)); + emit IERC20.Transfer(_from, ZERO_ADDRESS, _amount); + + // Look for the emit of the `Burn` event + vm.expectEmit(address(optimismSuperchainERC20)); + emit IOptimismSuperchainERC20Extension.Burn(_from, _amount); + + // Call the `burn` function with the bridge caller + vm.prank(L2_BRIDGE); + optimismSuperchainERC20.__superchainBurn(_from, _amount); + + // Check the total supply and balance of `_from` after the burn were updated correctly + assertEq(optimismSuperchainERC20.totalSupply(), _totalSupplyBefore - _amount); + assertEq(optimismSuperchainERC20.balanceOf(_from), _fromBalanceBefore - _amount); + } + + /// @notice Tests the `decimals` function always returns the correct value. + function testFuzz_decimals_succeeds(uint8 _decimals) public { + OptimismSuperchainERC20 _newSuperchainERC20 = _deploySuperchainERC20Proxy(REMOTE_TOKEN, NAME, SYMBOL, _decimals); + assertEq(_newSuperchainERC20.decimals(), _decimals); + } + + /// @notice Tests the `REMOTE_TOKEN` function always returns the correct value. + function testFuzz_remoteToken_succeeds(address _remoteToken) public { + OptimismSuperchainERC20 _newSuperchainERC20 = _deploySuperchainERC20Proxy(_remoteToken, NAME, SYMBOL, DECIMALS); + assertEq(_newSuperchainERC20.remoteToken(), _remoteToken); + } + + /// @notice Tests the `name` function always returns the correct value. + function testFuzz_name_succeeds(string memory _name) public { + OptimismSuperchainERC20 _newSuperchainERC20 = _deploySuperchainERC20Proxy(REMOTE_TOKEN, _name, SYMBOL, DECIMALS); + assertEq(_newSuperchainERC20.name(), _name); + } + + /// @notice Tests the `symbol` function always returns the correct value. + function testFuzz_symbol_succeeds(string memory _symbol) public { + OptimismSuperchainERC20 _newSuperchainERC20 = _deploySuperchainERC20Proxy(REMOTE_TOKEN, NAME, _symbol, DECIMALS); + assertEq(_newSuperchainERC20.symbol(), _symbol); + } + + /// @notice Tests that the `supportsInterface` function returns true for the `ISuperchainERC20` interface. + function test_supportInterface_succeeds() public view { + assertTrue(optimismSuperchainERC20.supportsInterface(type(IERC165).interfaceId)); + assertTrue(optimismSuperchainERC20.supportsInterface(type(IOptimismSuperchainERC20Extension).interfaceId)); + } + + /// @notice Tests that the `supportsInterface` function returns false for any other interface than the + /// `ISuperchainERC20` one. + function testFuzz_supportInterface_returnFalse(bytes4 _interfaceId) public view { + vm.assume(_interfaceId != type(IERC165).interfaceId); + vm.assume(_interfaceId != type(IOptimismSuperchainERC20Extension).interfaceId); + assertFalse(optimismSuperchainERC20.supportsInterface(_interfaceId)); + } +} diff --git a/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol index 10a65f66d2f4..372f85c061fa 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol @@ -85,9 +85,9 @@ contract SuperchainERC20Test is Test { vm.expectEmit(address(superchainERC20)); emit IERC20.Transfer(ZERO_ADDRESS, _to, _amount); - // Look for the emit of the `Mint` event + // Look for the emit of the `SuperchainMint` event vm.expectEmit(address(superchainERC20)); - emit ISuperchainERC20Extension.Mint(_to, _amount); + emit ISuperchainERC20Extension.SuperchainMint(_to, _amount); // Call the `mint` function with the bridge caller vm.prank(SUPERCHAIN_ERC20_BRIDGE); @@ -146,7 +146,7 @@ contract SuperchainERC20Test is Test { // Look for the emit of the `Burn` event vm.expectEmit(address(superchainERC20)); - emit ISuperchainERC20Extension.Burn(_from, _amount); + emit ISuperchainERC20Extension.SuperchainBurn(_from, _amount); // Call the `burn` function with the bridge caller vm.prank(SUPERCHAIN_ERC20_BRIDGE); From 959bb9057c3ab68200109f80497bfb708653f0f8 Mon Sep 17 00:00:00 2001 From: 0xDiscotech <131301107+0xDiscotech@users.noreply.github.com> Date: Tue, 1 Oct 2024 15:33:17 -0300 Subject: [PATCH 06/10] fix: tests --- .../src/L2/L2StandardBridgeInterop.sol | 4 +-- .../src/L2/OptimismSuperchainERC20.sol | 2 +- .../src/L2/SuperchainERC20Bridge.sol | 6 ++-- .../interfaces/IMintableAndBurnableERC20.sol | 4 +-- .../test/L2/L2StandardBridgeInterop.t.sol | 16 +++-------- .../test/L2/OptimismSuperchainERC20.t.sol | 28 +++++++++++-------- .../test/vendor/Initializable.t.sol | 2 +- 7 files changed, 29 insertions(+), 33 deletions(-) diff --git a/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol b/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol index 0061cfd11535..3383bd0d2733 100644 --- a/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol +++ b/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol @@ -52,8 +52,8 @@ contract L2StandardBridgeInterop is L2StandardBridge { function convert(address _from, address _to, uint256 _amount) external { _validatePair(_from, _to); - IMintableAndBurnableERC20(_from).__superchainMint(msg.sender, _amount); - IMintableAndBurnableERC20(_to).__superchainBurn(msg.sender, _amount); + IMintableAndBurnableERC20(_from).mint(msg.sender, _amount); + IMintableAndBurnableERC20(_to).burn(msg.sender, _amount); emit Converted(_from, _to, msg.sender, _amount); } diff --git a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol index 0442e21a7fe3..93f7e18664f5 100644 --- a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol @@ -84,7 +84,7 @@ contract OptimismSuperchainERC20 is SuperchainERC20, Initializable, ERC165, IOpt _mint(_to, _amount); - emit Burn(_to, _amount); + emit Mint(_to, _amount); } /// @notice Allows the L2StandardBridge and SuperchainERC20Bridge to burn tokens. diff --git a/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol b/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol index 015985a787f8..103fe5794a1d 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol @@ -6,7 +6,7 @@ import { Predeploys } from "src/libraries/Predeploys.sol"; // Interfaces import { ISuperchainERC20Bridge } from "src/L2/interfaces/ISuperchainERC20Bridge.sol"; -import { IMintableAndBurnableERC20 } from "src/L2/interfaces/IMintableAndBurnableERC20.sol"; +import { ISuperchainERC20 } from "src/L2/interfaces/ISuperchainERC20.sol"; import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; /// @custom:proxied true @@ -29,7 +29,7 @@ contract SuperchainERC20Bridge is ISuperchainERC20Bridge { /// @param _amount Amount of tokens to send. /// @param _chainId Chain ID of the destination chain. function sendERC20(address _token, address _to, uint256 _amount, uint256 _chainId) external { - IMintableAndBurnableERC20(_token).__superchainBurn(msg.sender, _amount); + ISuperchainERC20(_token).__superchainBurn(msg.sender, _amount); bytes memory message = abi.encodeCall(this.relayERC20, (_token, msg.sender, _to, _amount)); IL2ToL2CrossDomainMessenger(MESSENGER).sendMessage(_chainId, address(this), message); @@ -51,7 +51,7 @@ contract SuperchainERC20Bridge is ISuperchainERC20Bridge { uint256 source = IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSource(); - IMintableAndBurnableERC20(_token).__superchainMint(_to, _amount); + ISuperchainERC20(_token).__superchainMint(_to, _amount); emit RelayERC20(_token, _from, _to, _amount, source); } diff --git a/packages/contracts-bedrock/src/L2/interfaces/IMintableAndBurnableERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/IMintableAndBurnableERC20.sol index d6070df06d04..166fa0c8077f 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/IMintableAndBurnableERC20.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/IMintableAndBurnableERC20.sol @@ -9,10 +9,10 @@ interface IMintableAndBurnableERC20 is IERC20 { /// @notice Mints `_amount` of tokens to `_to`. /// @param _to Address to mint tokens to. /// @param _amount Amount of tokens to mint. - function __superchainMint(address _to, uint256 _amount) external; + function mint(address _to, uint256 _amount) external; /// @notice Burns `_amount` of tokens from `_from`. /// @param _from Address to burn tokens from. /// @param _amount Amount of tokens to burn. - function __superchainBurn(address _from, uint256 _amount) external; + function burn(address _from, uint256 _amount) external; } diff --git a/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol b/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol index 09d4ecefb5b6..dce9ba3774d5 100644 --- a/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol +++ b/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol @@ -203,14 +203,10 @@ contract L2StandardBridgeInterop_LegacyToSuper_Test is L2StandardBridgeInterop_T // Mock and expect the `burn` and `mint` functions _mockAndExpect( - _from, - abi.encodeWithSelector(IMintableAndBurnableERC20.__superchainBurn.selector, _caller, _amount), - abi.encode() + _from, abi.encodeWithSelector(IMintableAndBurnableERC20.burn.selector, _caller, _amount), abi.encode() ); _mockAndExpect( - _to, - abi.encodeWithSelector(IMintableAndBurnableERC20.__superchainMint.selector, _caller, _amount), - abi.encode() + _to, abi.encodeWithSelector(IMintableAndBurnableERC20.mint.selector, _caller, _amount), abi.encode() ); // Act @@ -365,14 +361,10 @@ contract L2StandardBridgeInterop_SuperToLegacy_Test is L2StandardBridgeInterop_T // Mock and expect the `burn` and `mint` functions _mockAndExpect( - _from, - abi.encodeWithSelector(IMintableAndBurnableERC20.__superchainBurn.selector, _caller, _amount), - abi.encode() + _from, abi.encodeWithSelector(IMintableAndBurnableERC20.burn.selector, _caller, _amount), abi.encode() ); _mockAndExpect( - _to, - abi.encodeWithSelector(IMintableAndBurnableERC20.__superchainMint.selector, _caller, _amount), - abi.encode() + _to, abi.encodeWithSelector(IMintableAndBurnableERC20.mint.selector, _caller, _amount), abi.encode() ); // Act diff --git a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol index 630eb5214f78..ce67bc12d116 100644 --- a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol @@ -16,7 +16,11 @@ import { IBeacon } from "@openzeppelin/contracts-v5/proxy/beacon/IBeacon.sol"; import { BeaconProxy } from "@openzeppelin/contracts-v5/proxy/beacon/BeaconProxy.sol"; // Target contract -import { OptimismSuperchainERC20, IOptimismSuperchainERC20Extension } from "src/L2/OptimismSuperchainERC20.sol"; +import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; +import { + IOptimismSuperchainERC20Extension, + IOptimismSuperchainERC20Errors +} from "src/L2/interfaces/IOptimismSuperchainERC20.sol"; import { ISuperchainERC20Errors } from "src/L2/interfaces/ISuperchainERC20.sol"; /// @title OptimismSuperchainERC20Test @@ -119,12 +123,12 @@ contract OptimismSuperchainERC20Test is Test { // Ensure the caller is not the bridge vm.assume(_caller != L2_BRIDGE); - // Expect the revert with `OnlySuperchainERC20Bridge` selector - vm.expectRevert(ISuperchainERC20Errors.OnlySuperchainERC20Bridge.selector); + // Expect the revert with `OnlyL2StandardBridge` selector + vm.expectRevert(IOptimismSuperchainERC20Errors.OnlyL2StandardBridge.selector); // Call the `mint` function with the non-bridge caller vm.prank(_caller); - optimismSuperchainERC20.__superchainMint(_to, _amount); + optimismSuperchainERC20.mint(_to, _amount); } /// @notice Tests the `mint` function reverts when the amount is zero. @@ -134,7 +138,7 @@ contract OptimismSuperchainERC20Test is Test { // Call the `mint` function with the zero address vm.prank(L2_BRIDGE); - optimismSuperchainERC20.__superchainMint({ _to: ZERO_ADDRESS, _amount: _amount }); + optimismSuperchainERC20.mint({ _to: ZERO_ADDRESS, _amount: _amount }); } /// @notice Tests the `mint` succeeds and emits the `Mint` event. @@ -156,7 +160,7 @@ contract OptimismSuperchainERC20Test is Test { // Call the `mint` function with the bridge caller vm.prank(L2_BRIDGE); - optimismSuperchainERC20.__superchainMint(_to, _amount); + optimismSuperchainERC20.mint(_to, _amount); // Check the total supply and balance of `_to` after the mint were updated correctly assertEq(optimismSuperchainERC20.totalSupply(), _totalSupplyBefore + _amount); @@ -168,12 +172,12 @@ contract OptimismSuperchainERC20Test is Test { // Ensure the caller is not the bridge vm.assume(_caller != L2_BRIDGE); - // Expect the revert with `OnlySuperchainERC20Bridge` selector - vm.expectRevert(ISuperchainERC20Errors.OnlySuperchainERC20Bridge.selector); + // Expect the revert with `OnlyL2StandardBridge` selector + vm.expectRevert(IOptimismSuperchainERC20Errors.OnlyL2StandardBridge.selector); // Call the `burn` function with the non-bridge caller vm.prank(_caller); - optimismSuperchainERC20.__superchainBurn(_from, _amount); + optimismSuperchainERC20.burn(_from, _amount); } /// @notice Tests the `burn` function reverts when the amount is zero. @@ -183,7 +187,7 @@ contract OptimismSuperchainERC20Test is Test { // Call the `burn` function with the zero address vm.prank(L2_BRIDGE); - optimismSuperchainERC20.__superchainBurn({ _from: ZERO_ADDRESS, _amount: _amount }); + optimismSuperchainERC20.burn({ _from: ZERO_ADDRESS, _amount: _amount }); } /// @notice Tests the `burn` burns the amount and emits the `Burn` event. @@ -193,7 +197,7 @@ contract OptimismSuperchainERC20Test is Test { // Mint some tokens to `_from` so then they can be burned vm.prank(L2_BRIDGE); - optimismSuperchainERC20.__superchainMint(_from, _amount); + optimismSuperchainERC20.mint(_from, _amount); // Get the total supply and balance of `_from` before the burn to compare later on the assertions uint256 _totalSupplyBefore = optimismSuperchainERC20.totalSupply(); @@ -209,7 +213,7 @@ contract OptimismSuperchainERC20Test is Test { // Call the `burn` function with the bridge caller vm.prank(L2_BRIDGE); - optimismSuperchainERC20.__superchainBurn(_from, _amount); + optimismSuperchainERC20.burn(_from, _amount); // Check the total supply and balance of `_from` after the burn were updated correctly assertEq(optimismSuperchainERC20.totalSupply(), _totalSupplyBefore - _amount); diff --git a/packages/contracts-bedrock/test/vendor/Initializable.t.sol b/packages/contracts-bedrock/test/vendor/Initializable.t.sol index 0228c7bb53b5..d5c1a9e5e4c3 100644 --- a/packages/contracts-bedrock/test/vendor/Initializable.t.sol +++ b/packages/contracts-bedrock/test/vendor/Initializable.t.sol @@ -401,7 +401,7 @@ contract Initializer_Test is Bridge_Initializer { excludes[0] = "src/L1/SystemConfigInterop.sol"; excludes[1] = "src/L1/OptimismPortalInterop.sol"; // Contract is currently not being deployed as part of the standard deployment script. - excludes[2] = "src/L2/SuperchainERC20.sol"; + excludes[2] = "src/L2/OptimismSuperchainERC20.sol"; // Periphery contracts don't get deployed as part of the standard deployment script. excludes[3] = "src/periphery/*"; // TODO: Deployment script is currently "broken" in the sense that it doesn't properly From 0f1fc2c091425bb0012b6a5747f84f60ee30c247 Mon Sep 17 00:00:00 2001 From: agusduha Date: Tue, 1 Oct 2024 16:38:51 -0300 Subject: [PATCH 07/10] fix: natspecs --- .../src/L2/L2StandardBridgeInterop.sol | 14 ++--- .../src/L2/OptimismSuperchainERC20.sol | 57 ++++++++++--------- .../src/L2/OptimismSuperchainERC20Beacon.sol | 4 +- .../src/L2/OptimismSuperchainERC20Factory.sol | 18 +++--- .../src/L2/SuperchainERC20.sol | 13 ++--- .../IOptimismSuperchainERC20Factory.sol | 10 ++-- .../src/libraries/Predeploys.sol | 2 +- .../test/L2/L2StandardBridgeInterop.t.sol | 8 +-- 8 files changed, 64 insertions(+), 62 deletions(-) diff --git a/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol b/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol index 3383bd0d2733..f01a66dacc47 100644 --- a/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol +++ b/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol @@ -30,7 +30,7 @@ error InvalidTokenPair(); /// @title L2StandardBridgeInterop /// @notice The L2StandardBridgeInterop is an extension of the L2StandardBridge that allows for /// the conversion of tokens between legacy tokens (OptimismMintableERC20 or StandardL2ERC20) -/// and SuperchainERC20 tokens. +/// and OptimismSuperchainERC20 tokens. contract L2StandardBridgeInterop is L2StandardBridge { /// @notice Emitted when a conversion is made. /// @param from The token being converted from. @@ -40,9 +40,9 @@ contract L2StandardBridgeInterop is L2StandardBridge { event Converted(address indexed from, address indexed to, address indexed caller, uint256 amount); /// @notice Semantic version. - /// @custom:semver +interop-beta.2 + /// @custom:semver +interop-beta.1 function version() public pure override returns (string memory) { - return string.concat(super.version(), "+interop-beta.2"); + return string.concat(super.version(), "+interop-beta.1"); } /// @notice Converts `amount` of `from` token to `to` token. @@ -52,8 +52,8 @@ contract L2StandardBridgeInterop is L2StandardBridge { function convert(address _from, address _to, uint256 _amount) external { _validatePair(_from, _to); - IMintableAndBurnableERC20(_from).mint(msg.sender, _amount); - IMintableAndBurnableERC20(_to).burn(msg.sender, _amount); + IMintableAndBurnableERC20(_from).burn(msg.sender, _amount); + IMintableAndBurnableERC20(_to).mint(msg.sender, _amount); emit Converted(_from, _to, msg.sender, _amount); } @@ -75,14 +75,14 @@ contract L2StandardBridgeInterop is L2StandardBridge { /// @notice Validates that the tokens are deployed by the correct factory. /// @param _legacyAddr The legacy token address (OptimismMintableERC20 or StandardL2ERC20). - /// @param _superAddr The SuperchainERC20 address. + /// @param _superAddr The OptimismSuperchainERC20 address. function _validateFactories(address _legacyAddr, address _superAddr) internal view { // 2. Valid legacy check address _legacyRemoteToken = IOptimismERC20Factory(Predeploys.OPTIMISM_MINTABLE_ERC20_FACTORY).deployments(_legacyAddr); if (_legacyRemoteToken == address(0)) revert InvalidLegacyERC20Address(); - // 3. Valid SuperchainERC20 check + // 3. Valid OptimismSuperchainERC20 check address _superRemoteToken = IOptimismERC20Factory(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY).deployments(_superAddr); if (_superRemoteToken == address(0)) revert InvalidSuperchainERC20Address(); diff --git a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol index 93f7e18664f5..193c900b8bbd 100644 --- a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol @@ -1,10 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; -import { Predeploys } from "src/libraries/Predeploys.sol"; -import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; import { IOptimismSuperchainERC20Extension } from "src/L2/interfaces/IOptimismSuperchainERC20.sol"; +import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; +import { ISemver } from "src/universal/interfaces/ISemver.sol"; +import { Predeploys } from "src/libraries/Predeploys.sol"; import { ERC165 } from "@openzeppelin/contracts-v5/utils/introspection/ERC165.sol"; +import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; import { Initializable } from "@openzeppelin/contracts-v5/proxy/utils/Initializable.sol"; /// @custom:proxied true @@ -16,14 +18,14 @@ import { Initializable } from "@openzeppelin/contracts-v5/proxy/utils/Initializa /// also enables the inverse conversion path. /// Moreover, it builds on top of the L2ToL2CrossDomainMessenger for both replay protection and domain binding. contract OptimismSuperchainERC20 is SuperchainERC20, Initializable, ERC165, IOptimismSuperchainERC20Extension { - /// @notice Storage slot that the SuperchainERC20Metadata struct is stored at. - /// keccak256(abi.encode(uint256(keccak256("SuperchainERC20.metadata")) - 1)) & ~bytes32(uint256(0xff)); + /// @notice Storage slot that the OptimismSuperchainERC20Metadata struct is stored at. + /// keccak256(abi.encode(uint256(keccak256("optimismSuperchainERC20.metadata")) - 1)) & ~bytes32(uint256(0xff)); bytes32 internal constant OPTIMISM_SUPERCHAIN_ERC20_METADATA_SLOT = 0x07f04e84143df95a6373fcf376312ae41da81a193a3089073a54f47a74d8fb00; - /// @notice Storage struct for the SuperchainERC20 metadata. - /// @custom:storage-location erc7201:SuperchainERC20.metadata - struct SuperchainERC20Metadata { + /// @notice Storage struct for the OptimismSuperchainERC20 metadata. + /// @custom:storage-location erc7201:optimismSuperchainERC20.metadata + struct OptimismSuperchainERC20Metadata { /// @notice Address of the corresponding version of this token on the remote chain. address remoteToken; /// @notice Name of the token @@ -34,18 +36,30 @@ contract OptimismSuperchainERC20 is SuperchainERC20, Initializable, ERC165, IOpt uint8 decimals; } - /// @notice Constructs the SuperchainERC20 contract. - constructor() { - _disableInitializers(); - } - - /// @notice Returns the storage for the SuperchainERC20Metadata. - function _getStorage() internal pure returns (SuperchainERC20Metadata storage _storage) { + /// @notice Returns the storage for the OptimismSuperchainERC20Metadata. + function _getStorage() private pure returns (OptimismSuperchainERC20Metadata storage _storage) { assembly { _storage.slot := OPTIMISM_SUPERCHAIN_ERC20_METADATA_SLOT } } + /// @notice A modifier that only allows the L2StandardBridge to call + modifier onlyL2StandardBridge() { + if (msg.sender != Predeploys.L2_STANDARD_BRIDGE) { + revert OnlyL2StandardBridge(); + } + _; + } + + /// @notice Semantic version. + /// @custom:semver 1.0.0-beta.6 + string public constant override version = "1.0.0-beta.6"; + + /// @notice Constructs the OptimismSuperchainERC20 contract. + constructor() { + _disableInitializers(); + } + /// @notice Initializes the contract. /// @param _remoteToken Address of the corresponding remote token. /// @param _name ERC20 name. @@ -60,23 +74,14 @@ contract OptimismSuperchainERC20 is SuperchainERC20, Initializable, ERC165, IOpt external initializer { - SuperchainERC20Metadata storage _storage = _getStorage(); + OptimismSuperchainERC20Metadata storage _storage = _getStorage(); _storage.remoteToken = _remoteToken; _storage.name = _name; _storage.symbol = _symbol; _storage.decimals = _decimals; } - /// @notice Semantic version. - /// @custom:semver 1.0.0-beta - string public constant override version = "1.0.0-beta"; - - modifier onlyL2StandardBridge() { - if (msg.sender != Predeploys.L2_STANDARD_BRIDGE) revert OnlyL2StandardBridge(); - _; - } - - /// @notice Allows the L2StandardBridge and SuperchainERC20Bridge to mint tokens. + /// @notice Allows the L2StandardBridge 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 onlyL2StandardBridge { @@ -87,7 +92,7 @@ contract OptimismSuperchainERC20 is SuperchainERC20, Initializable, ERC165, IOpt emit Mint(_to, _amount); } - /// @notice Allows the L2StandardBridge and SuperchainERC20Bridge to burn tokens. + /// @notice Allows the L2StandardBridge 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 onlyL2StandardBridge { diff --git a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Beacon.sol b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Beacon.sol index 6935793a4765..d1160819a2ec 100644 --- a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Beacon.sol +++ b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Beacon.sol @@ -7,9 +7,9 @@ import { ISemver } from "src/universal/interfaces/ISemver.sol"; /// @custom:proxied /// @custom:predeployed 0x4200000000000000000000000000000000000027 /// @title OptimismSuperchainERC20Beacon -/// @notice OptimismSuperchainERC20Beacon is the beacon proxy for the SuperchainERC20 implementation. +/// @notice OptimismSuperchainERC20Beacon is the beacon proxy for the OptimismSuperchainERC20 implementation. contract OptimismSuperchainERC20Beacon is IBeacon, ISemver { - /// @notice Address of the SuperchainERC20 implementation. + /// @notice Address of the OptimismSuperchainERC20 implementation. address internal immutable IMPLEMENTATION; /// @notice Semantic version. diff --git a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol index f34d06d8505d..8e61dca87b76 100644 --- a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol +++ b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol @@ -11,15 +11,15 @@ import { CREATE3 } from "@rari-capital/solmate/src/utils/CREATE3.sol"; /// @custom:proxied /// @custom:predeployed 0x4200000000000000000000000000000000000026 /// @title OptimismSuperchainERC20Factory -/// @notice OptimismSuperchainERC20Factory is a factory contract that deploys SuperchainERC20 Beacon Proxies +/// @notice OptimismSuperchainERC20Factory is a factory contract that deploys OptimismSuperchainERC20 Beacon Proxies /// using CREATE3. contract OptimismSuperchainERC20Factory is IOptimismSuperchainERC20Factory, ISemver { - /// @notice Mapping of the deployed SuperchainERC20 to the remote token address. + /// @notice Mapping of the deployed OptimismSuperchainERC20 to the remote token address. /// This is used to keep track of the token deployments. mapping(address superchainToken => address remoteToken) public deployments; - /// @notice Emitted when an SuperchainERC20 is deployed. - /// @param superchainToken Address of the SuperchainERC20 deployment. + /// @notice Emitted when an OptimismSuperchainERC20 is deployed. + /// @param superchainToken Address of the OptimismSuperchainERC20 deployment. /// @param remoteToken Address of the corresponding token on the remote chain. /// @param deployer Address of the account that deployed the token. event OptimismSuperchainERC20Created( @@ -30,12 +30,12 @@ contract OptimismSuperchainERC20Factory is IOptimismSuperchainERC20Factory, ISem /// @custom:semver 1.0.0-beta.3 string public constant version = "1.0.0-beta.3"; - /// @notice Deploys a SuperchainERC20 Beacon Proxy using CREATE3. + /// @notice Deploys a OptimismSuperchainERC20 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. + /// @param _name Name of the OptimismSuperchainERC20. + /// @param _symbol Symbol of the OptimismSuperchainERC20. + /// @param _decimals Decimals of the OptimismSuperchainERC20. + /// @return _superchainERC20 Address of the OptimismSuperchainERC20 deployment. function deploy( address _remoteToken, string memory _name, diff --git a/packages/contracts-bedrock/src/L2/SuperchainERC20.sol b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol index 9661e3406bde..c67cb8240621 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol @@ -8,22 +8,19 @@ import { ERC20 } from "@solady/tokens/ERC20.sol"; /// @title SuperchainERC20 /// @notice SuperchainERC20 is a standard extension of the base ERC20 token contract that unifies ERC20 token -/// bridging to make it fungible across the Superchain. This construction allows the L2StandardBridge to burn -/// and mint tokens. This makes it possible to convert a valid OptimismMintableERC20 token to a -/// SuperchainERC20 token, turning it fungible and interoperable across the superchain. Likewise, it -/// also enables the inverse conversion path. -/// Moreover, it builds on top of the L2ToL2CrossDomainMessenger for both replay protection and domain binding. +/// bridging to make it fungible across the Superchain. This construction allows the SuperchainERC20Bridge to +/// burn and mint tokens. abstract contract SuperchainERC20 is ERC20, ISuperchainERC20Extension, ISemver { - /// @notice A modifier that only allows the bridge to call + /// @notice A modifier that only allows the SuperchainERC20Bridge to call modifier onlySuperchainERC20Bridge() { if (msg.sender != Predeploys.SUPERCHAIN_ERC20_BRIDGE) revert OnlySuperchainERC20Bridge(); _; } /// @notice Semantic version. - /// @custom:semver 1.0.0-beta.6 + /// @custom:semver 1.0.0-beta.1 function version() external pure virtual returns (string memory) { - return "1.0.0-beta.6"; + return "1.0.0-beta.1"; } /// @notice Allows the SuperchainERC20Bridge to mint tokens. diff --git a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20Factory.sol b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20Factory.sol index 9934a6dee877..aa23405fa527 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20Factory.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20Factory.sol @@ -6,12 +6,12 @@ import { IOptimismERC20Factory } from "./IOptimismERC20Factory.sol"; /// @title IOptimismSuperchainERC20Factory /// @notice Interface for OptimismSuperchainERC20Factory. interface IOptimismSuperchainERC20Factory is IOptimismERC20Factory { - /// @notice Deploys a SuperchainERC20 Beacon Proxy using CREATE3. + /// @notice Deploys a OptimismSuperchainERC20 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. + /// @param _name Name of the OptimismSuperchainERC20. + /// @param _symbol Symbol of the OptimismSuperchainERC20. + /// @param _decimals Decimals of the OptimismSuperchainERC20. + /// @return _superchainERC20 Address of the OptimismSuperchainERC20 deployment. function deploy( address _remoteToken, string memory _name, diff --git a/packages/contracts-bedrock/src/libraries/Predeploys.sol b/packages/contracts-bedrock/src/libraries/Predeploys.sol index 891ab78c648f..5bc1d75a1f06 100644 --- a/packages/contracts-bedrock/src/libraries/Predeploys.sol +++ b/packages/contracts-bedrock/src/libraries/Predeploys.sol @@ -102,7 +102,7 @@ library Predeploys { address internal constant OPTIMISM_SUPERCHAIN_ERC20_BEACON = 0x4200000000000000000000000000000000000027; // TODO: Precalculate the address of the implementation contract - /// @notice Arbitrary address of the SuperchainERC20 implementation contract. + /// @notice Arbitrary address of the OptimismSuperchainERC20 implementation contract. address internal constant OPTIMISM_SUPERCHAIN_ERC20 = 0xB9415c6cA93bdC545D4c5177512FCC22EFa38F28; /// @notice Address of the SuperchainERC20Bridge predeploy. diff --git a/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol b/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol index dce9ba3774d5..96f4ad4ba61f 100644 --- a/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol +++ b/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol @@ -58,9 +58,9 @@ contract L2StandardBridgeInterop_Test is Bridge_Initializer { } } -/// @notice Test suite when converting from a legacy token to a SuperchainERC20 token +/// @notice Test suite when converting from a legacy token to a OptimismSuperchainERC20 token contract L2StandardBridgeInterop_LegacyToSuper_Test is L2StandardBridgeInterop_Test { - /// @notice Set up the test for converting from a legacy token to a SuperchainERC20 token + /// @notice Set up the test for converting from a legacy token to a OptimismSuperchainERC20 token function _setUpLegacyToSuper(address _from, address _to) internal { // Assume _assumeAddress(_from); @@ -215,9 +215,9 @@ contract L2StandardBridgeInterop_LegacyToSuper_Test is L2StandardBridgeInterop_T } } -/// @notice Test suite when converting from a SuperchainERC20 token to a legacy token +/// @notice Test suite when converting from a OptimismSuperchainERC20 token to a legacy token contract L2StandardBridgeInterop_SuperToLegacy_Test is L2StandardBridgeInterop_Test { - /// @notice Set up the test for converting from a SuperchainERC20 token to a legacy token + /// @notice Set up the test for converting from a OptimismSuperchainERC20 token to a legacy token function _setUpSuperToLegacy(address _from, address _to) internal { // Assume _assumeAddress(_from); From f48a1a53350c905048260a2aa1d934e2f41bc0e2 Mon Sep 17 00:00:00 2001 From: agusduha Date: Tue, 1 Oct 2024 16:57:01 -0300 Subject: [PATCH 08/10] fix: add semmver lock and snapshots --- packages/contracts-bedrock/semver-lock.json | 12 ++- .../abi/OptimismSuperchainERC20.json | 81 ++++++++++++++++++- .../src/L2/L2StandardBridgeInterop.sol | 2 +- 3 files changed, 89 insertions(+), 6 deletions(-) diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index 1cc71c0e4dc7..b77e5c55e062 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -116,8 +116,8 @@ "sourceCodeHash": "0x4b806cc85cead74c8df34ab08f4b6c6a95a1a387a335ec8a7cb2de4ea4e1cf41" }, "src/L2/OptimismSuperchainERC20.sol": { - "initCodeHash": "0x749dacbd29aad60c71c1e1878b21854c796759fb7a5ddc549b96ab9e39bb62b8", - "sourceCodeHash": "0xd067ddabbeb6fe1c3886100b16c905b57dfd44f5e6e893323217fe4e642285d4" + "initCodeHash": "0xfdb4acd9496a7d3949f71e7e98786ff909730a8ad62d33cf7e29765dceecc6db", + "sourceCodeHash": "0xd91a905e74bfe3572dc706eb5fbf5d3d4da4d28a1ee6cac3f3c6fbc94ec3a279" }, "src/L2/OptimismSuperchainERC20Beacon.sol": { "initCodeHash": "0x99ce8095b23c124850d866cbc144fee6cee05dbc6bb5d83acadfe00b90cf42c7", @@ -131,9 +131,13 @@ "initCodeHash": "0x2e6551705e493bacba8cffe22e564d5c401ae5bb02577a5424e0d32784e13e74", "sourceCodeHash": "0xd56922cb04597dea469c65e5a49d4b3c50c171e603601e6f41da9517cae0b11a" }, + "src/L2/SuperchainERC20.sol": { + "initCodeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "sourceCodeHash": "0xddd32f6332510e63d8c98d70321e058b71eda02e6b32a9b6e41540c58d1e653e" + }, "src/L2/SuperchainERC20Bridge.sol": { - "initCodeHash": "0x802574bf35587e9a8dc2416e91b9fd1411c75d219545b8b55d25a75452459b10", - "sourceCodeHash": "0xb11ce94fd6165d8ca86eebafc7235e0875380d1a5d4e8b267ff0c6477083b21c" + "initCodeHash": "0x77d3173e1f269f6bf57f85685abecb4979a7d7d3c672c7afa2a648b66228122f", + "sourceCodeHash": "0xf0749a0b9366a06981d2a8f66a55ce1a37e3d5d7dd77704f618741c18cd79009" }, "src/L2/SuperchainWETH.sol": { "initCodeHash": "0xf30071df59d85e0e8a552845031aca8d6f0261762e1b4ea1b28ff30379eaa20e", diff --git a/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json b/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json index 62dfe3ea6191..1c7e5b2f4e8e 100644 --- a/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json +++ b/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json @@ -17,6 +17,42 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "_from", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "__superchainBurn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "__superchainMint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -436,6 +472,44 @@ "name": "Mint", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "SuperchainBurn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "SuperchainMint", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -498,7 +572,12 @@ }, { "inputs": [], - "name": "OnlyAuthorizedBridge", + "name": "OnlyL2StandardBridge", + "type": "error" + }, + { + "inputs": [], + "name": "OnlySuperchainERC20Bridge", "type": "error" }, { diff --git a/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol b/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol index f01a66dacc47..eb25a406ede7 100644 --- a/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol +++ b/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol @@ -19,7 +19,7 @@ error InvalidDecimals(); /// @notice Thrown when the legacy address is not found in the OptimismMintableERC20Factory. error InvalidLegacyERC20Address(); -/// @notice Thrown when the SuperchainERC20 address is not found in the OptimismSuperchainERC20Factory. +/// @notice Thrown when the OptimismSuperchainERC20 address is not found in the OptimismSuperchainERC20Factory. error InvalidSuperchainERC20Address(); /// @notice Thrown when the remote addresses of the tokens are not the same. From 8965557ff0dac48a3dbe4ac42d7323e7f992e947 Mon Sep 17 00:00:00 2001 From: agusduha Date: Tue, 1 Oct 2024 17:20:22 -0300 Subject: [PATCH 09/10] fix: remove unused imports --- packages/contracts-bedrock/semver-lock.json | 2 +- packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index b77e5c55e062..57e415387b68 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -117,7 +117,7 @@ }, "src/L2/OptimismSuperchainERC20.sol": { "initCodeHash": "0xfdb4acd9496a7d3949f71e7e98786ff909730a8ad62d33cf7e29765dceecc6db", - "sourceCodeHash": "0xd91a905e74bfe3572dc706eb5fbf5d3d4da4d28a1ee6cac3f3c6fbc94ec3a279" + "sourceCodeHash": "0x2502433e4b622e1697ca071f91a95b08fa40fdb03bfd958c44b2033a47df2010" }, "src/L2/OptimismSuperchainERC20Beacon.sol": { "initCodeHash": "0x99ce8095b23c124850d866cbc144fee6cee05dbc6bb5d83acadfe00b90cf42c7", diff --git a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol index 193c900b8bbd..6db110dd5f5f 100644 --- a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol @@ -2,8 +2,6 @@ pragma solidity 0.8.25; import { IOptimismSuperchainERC20Extension } from "src/L2/interfaces/IOptimismSuperchainERC20.sol"; -import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; import { ERC165 } from "@openzeppelin/contracts-v5/utils/introspection/ERC165.sol"; import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; From 8a94c49cd637a7e11413a5a48f2b2e568db627ea Mon Sep 17 00:00:00 2001 From: agusduha Date: Tue, 1 Oct 2024 17:39:00 -0300 Subject: [PATCH 10/10] fix: natspecs --- .../src/L2/interfaces/IOptimismSuperchainERC20.sol | 12 ++++++------ .../test/L2/OptimismSuperchainERC20.t.sol | 1 - 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol index 5fe204b9236c..3000f0d85be0 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol @@ -5,15 +5,15 @@ pragma solidity ^0.8.0; import { ISuperchainERC20, ISuperchainERC20Extension } from "src/L2/interfaces/ISuperchainERC20.sol"; import { IERC20Solady } from "src/vendor/interfaces/IERC20Solady.sol"; -/// @title ISuperchainERC20Errors -/// @notice Interface containing the errors added in the SuperchainERC20 implementation. +/// @title IOptimismSuperchainERC20Errors +/// @notice Interface containing the errors added in the OptimismSuperchainERC20 implementation. interface IOptimismSuperchainERC20Errors { /// @notice Thrown when attempting to mint or burn tokens and the function caller is not the L2StandardBridge error OnlyL2StandardBridge(); } -/// @title ISuperchainERC20Extension -/// @notice This interface is available on the SuperchainERC20 contract. +/// @title IOptimismSuperchainERC20Extension +/// @notice This interface is available on the OptimismSuperchainERC20 contract. interface IOptimismSuperchainERC20Extension is ISuperchainERC20Extension, IOptimismSuperchainERC20Errors { /// @notice Emitted whenever tokens are minted for an account. /// @param account Address of the account tokens are being minted for. @@ -39,6 +39,6 @@ interface IOptimismSuperchainERC20Extension is ISuperchainERC20Extension, IOptim function remoteToken() external view returns (address); } -/// @title ISuperchainERC20 -/// @notice Combines Solady's ERC20 interface with the SuperchainERC20Extension interface. +/// @title IOptimismSuperchainERC20 +/// @notice Combines Solady's ERC20 interface with the IOptimismSuperchainERC20Extension interface. interface IOptimismSuperchainERC20 is IERC20Solady, IOptimismSuperchainERC20Extension { } diff --git a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol index ce67bc12d116..115e6f268bb2 100644 --- a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol @@ -8,7 +8,6 @@ import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; // Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; import { IERC20 } from "@openzeppelin/contracts-v5/token/ERC20/IERC20.sol"; -import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; 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";