From 25345553ab022e4fd872866537296df52fc73b01 Mon Sep 17 00:00:00 2001 From: AgusDuha <81362284+agusduha@users.noreply.github.com> Date: Thu, 22 Aug 2024 11:57:49 -0300 Subject: [PATCH] test: add superchain erc20 factory tests (#25) * test: add superchain erc20 factory tests * test: add erc20 asserts --- packages/contracts-bedrock/.gas-snapshot | 8 +- .../contracts-bedrock/scripts/Artifacts.s.sol | 4 + .../contracts-bedrock/scripts/L2Genesis.s.sol | 14 +++ .../test/L2/L2StandardBridgeInterop.t.sol | 15 +-- .../test/L2/OptimismSuperchainERC20.t.sol | 32 ++++- .../L2/OptimismSuperchainERC20Factory.t.sol | 111 ++++++++++++++++++ .../contracts-bedrock/test/L2Genesis.t.sol | 4 +- .../contracts-bedrock/test/setup/Setup.sol | 7 ++ 8 files changed, 176 insertions(+), 19 deletions(-) create mode 100644 packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Factory.t.sol diff --git a/packages/contracts-bedrock/.gas-snapshot b/packages/contracts-bedrock/.gas-snapshot index b3ea3b88545e..bf6d48b6d342 100644 --- a/packages/contracts-bedrock/.gas-snapshot +++ b/packages/contracts-bedrock/.gas-snapshot @@ -1,11 +1,11 @@ GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 369356) GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2967496) -GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 564483) -GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4076526) +GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 564489) +GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4076532) GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_0() (gas: 466947) GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 3512629) GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (gas: 72624) -GasBenchMark_L2OutputOracle:test_proposeL2Output_benchmark() (gas: 92973) +GasBenchMark_L2OutputOracle:test_proposeL2Output_benchmark() (gas: 92970) GasBenchMark_OptimismPortal:test_depositTransaction_benchmark() (gas: 68433) GasBenchMark_OptimismPortal:test_depositTransaction_benchmark_1() (gas: 68903) -GasBenchMark_OptimismPortal:test_proveWithdrawalTransaction_benchmark() (gas: 155618) \ No newline at end of file +GasBenchMark_OptimismPortal:test_proveWithdrawalTransaction_benchmark() (gas: 155615) \ No newline at end of file diff --git a/packages/contracts-bedrock/scripts/Artifacts.s.sol b/packages/contracts-bedrock/scripts/Artifacts.s.sol index b054d2e27254..f912c7f03824 100644 --- a/packages/contracts-bedrock/scripts/Artifacts.s.sol +++ b/packages/contracts-bedrock/scripts/Artifacts.s.sol @@ -152,6 +152,10 @@ abstract contract Artifacts { return payable(Predeploys.SCHEMA_REGISTRY); } else if (digest == keccak256(bytes("EAS"))) { return payable(Predeploys.EAS); + } else if (digest == keccak256(bytes("OptimismSuperchainERC20Factory"))) { + return payable(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY); + } else if (digest == keccak256(bytes("OptimismSuperchainERC20Beacon"))) { + return payable(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON); } return payable(address(0)); } diff --git a/packages/contracts-bedrock/scripts/L2Genesis.s.sol b/packages/contracts-bedrock/scripts/L2Genesis.s.sol index 7d4568653936..efa7baf1fa79 100644 --- a/packages/contracts-bedrock/scripts/L2Genesis.s.sol +++ b/packages/contracts-bedrock/scripts/L2Genesis.s.sol @@ -254,6 +254,8 @@ contract L2Genesis is Deployer { setL2ToL2CrossDomainMessenger(); // 23 setSuperchainWETH(); // 24 setETHLiquidity(); // 25 + setOptimismSuperchainERC20Factory(); // 26 + setOptimismSuperchainERC20Beacon(); // 27 } } @@ -505,6 +507,18 @@ contract L2Genesis is Deployer { _setImplementationCode(Predeploys.SUPERCHAIN_WETH); } + /// @notice This predeploy is following the safety invariant #1. + /// This contract has no initializer. + function setOptimismSuperchainERC20Factory() internal { + _setImplementationCode(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY); + } + + /// @notice This predeploy is following the safety invariant #1. + /// This contract has no initializer. + function setOptimismSuperchainERC20Beacon() internal { + _setImplementationCode(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON); + } + /// @notice Sets all the preinstalls. /// Warning: the creator-accounts of the preinstall contracts have 0 nonce values. /// When performing a regular user-initiated contract-creation of a preinstall, diff --git a/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol b/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol index ca8e968fe9e4..2a4fb8ce201d 100644 --- a/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol +++ b/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol @@ -18,9 +18,6 @@ import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/I import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import { IOptimismMintableERC20 } from "src/universal/IOptimismMintableERC20.sol"; -// TODO: Replace Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY with optimismSuperchainERC20Factory -import { Predeploys } from "src/libraries/Predeploys.sol"; - contract L2StandardBridgeInterop_Test is Bridge_Initializer { /// @notice Emitted when a conversion is made. event Converted(address indexed from, address indexed to, address indexed caller, uint256 amount); @@ -145,7 +142,7 @@ contract L2StandardBridgeInterop_LegacyToSuper_Test is L2StandardBridgeInterop_T _mockDeployments(address(l2OptimismMintableERC20Factory), _from, _remoteToken); // Mock the superchain factory to return address(0) - _mockDeployments(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY, _to, address(0)); + _mockDeployments(address(l2OptimismSuperchainERC20Factory), _to, address(0)); // Expect the revert with `InvalidSuperchainERC20Address` selector vm.expectRevert(InvalidSuperchainERC20Address.selector); @@ -176,7 +173,7 @@ contract L2StandardBridgeInterop_LegacyToSuper_Test is L2StandardBridgeInterop_T _mockDeployments(address(l2OptimismMintableERC20Factory), _from, _fromRemoteToken); // Mock the superchain factory to return `_toRemoteToken` - _mockDeployments(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY, _to, _toRemoteToken); + _mockDeployments(address(l2OptimismSuperchainERC20Factory), _to, _toRemoteToken); // Expect the revert with `InvalidTokenPair` selector vm.expectRevert(InvalidTokenPair.selector); @@ -203,7 +200,7 @@ contract L2StandardBridgeInterop_LegacyToSuper_Test is L2StandardBridgeInterop_T // Mock the legacy and superchain factory to return `_remoteToken` _mockDeployments(address(l2OptimismMintableERC20Factory), _from, _remoteToken); - _mockDeployments(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY, _to, _remoteToken); + _mockDeployments(address(l2OptimismSuperchainERC20Factory), _to, _remoteToken); // Expect the `Converted` event to be emitted vm.expectEmit(true, true, true, true, address(l2StandardBridge)); @@ -294,7 +291,7 @@ contract L2StandardBridgeInterop_SuperToLegacy_Test is L2StandardBridgeInterop_T _mockDeployments(address(l2OptimismMintableERC20Factory), _to, _remoteToken); // Mock the superchain factory to return address(0) - _mockDeployments(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY, _from, address(0)); + _mockDeployments(address(l2OptimismSuperchainERC20Factory), _from, address(0)); // Expect the revert with `InvalidSuperchainERC20Address` selector vm.expectRevert(InvalidSuperchainERC20Address.selector); @@ -325,7 +322,7 @@ contract L2StandardBridgeInterop_SuperToLegacy_Test is L2StandardBridgeInterop_T _mockDeployments(address(l2OptimismMintableERC20Factory), _to, _fromRemoteToken); // Mock the superchain factory to return `_toRemoteToken` - _mockDeployments(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY, _from, _toRemoteToken); + _mockDeployments(address(l2OptimismSuperchainERC20Factory), _from, _toRemoteToken); // Expect the revert with `InvalidTokenPair` selector vm.expectRevert(InvalidTokenPair.selector); @@ -352,7 +349,7 @@ contract L2StandardBridgeInterop_SuperToLegacy_Test is L2StandardBridgeInterop_T // Mock the legacy and superchain factory to return `_remoteToken` _mockDeployments(address(l2OptimismMintableERC20Factory), _to, _remoteToken); - _mockDeployments(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY, _from, _remoteToken); + _mockDeployments(address(l2OptimismSuperchainERC20Factory), _from, _remoteToken); // Expect the `Converted` event to be emitted vm.expectEmit(true, true, true, true, address(l2StandardBridge)); diff --git a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol index 84580fdd8687..73377735192c 100644 --- a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol @@ -3,14 +3,16 @@ 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/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 { @@ -40,9 +42,32 @@ contract OptimismSuperchainERC20Test is Test { /// @notice Sets up the test suite. function setUp() public { superchainERC20Impl = new OptimismSuperchainERC20(); + + // Deploy the OptimismSuperchainERC20Beacon contract + _deployBeacon(); + superchainERC20 = _deploySuperchainERC20Proxy(REMOTE_TOKEN, NAME, SYMBOL, DECIMALS); } + /// @notice Deploy the OptimismSuperchainERC20Beacon predeploy contract + function _deployBeacon() internal { + // Deploy the OptimismSuperchainERC20Beacon implementation + address _addr = Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON; + address _impl = Predeploys.predeployToCodeNamespace(_addr); + vm.etch(_impl, vm.getDeployedCode("OptimismSuperchainERC20Beacon.sol:OptimismSuperchainERC20Beacon")); + + // Deploy the ERC1967Proxy contract at the Predeploy + bytes memory code = vm.getDeployedCode("universal/Proxy.sol:Proxy"); + vm.etch(_addr, code); + EIP1967Helper.setAdmin(_addr, Predeploys.PROXY_ADMIN); + EIP1967Helper.setImplementation(_addr, _impl); + + // Mock implementation address + vm.mockCall( + _impl, abi.encodeWithSelector(IBeacon.implementation.selector), abi.encode(address(superchainERC20Impl)) + ); + } + /// @notice Helper function to deploy a proxy of the OptimismSuperchainERC20 contract. function _deploySuperchainERC20Proxy( address _remoteToken, @@ -55,9 +80,8 @@ contract OptimismSuperchainERC20Test is Test { { return OptimismSuperchainERC20( address( - // TODO: Use the SuperchainERC20 Beacon Proxy - new ERC1967Proxy( - address(superchainERC20Impl), + new BeaconProxy( + Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON, abi.encodeCall(OptimismSuperchainERC20.initialize, (_remoteToken, _name, _symbol, _decimals)) ) ) diff --git a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Factory.t.sol b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Factory.t.sol new file mode 100644 index 000000000000..b82a40bf2bc7 --- /dev/null +++ b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Factory.t.sol @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +// Testing utilities +import { Test } from "forge-std/Test.sol"; +import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; + +// Libraries +import { Predeploys } from "src/libraries/Predeploys.sol"; +import { CREATE3, Bytes32AddressLib } from "@rari-capital/solmate/src/utils/CREATE3.sol"; +import { IBeacon } from "@openzeppelin/contracts-v5/proxy/beacon/IBeacon.sol"; + +// Target contract +import { OptimismSuperchainERC20Factory, OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20Factory.sol"; + +/// @title OptimismSuperchainERC20FactoryTest +/// @notice Contract for testing the OptimismSuperchainERC20Factory contract. +contract OptimismSuperchainERC20FactoryTest is Test { + using Bytes32AddressLib for bytes32; + + OptimismSuperchainERC20 public superchainERC20Impl; + OptimismSuperchainERC20Factory public superchainERC20Factory; + + /// @notice Sets up the test suite. + function setUp() public { + superchainERC20Impl = new OptimismSuperchainERC20(); + + // Deploy the OptimismSuperchainERC20Beacon contract + _deployBeacon(); + + superchainERC20Factory = new OptimismSuperchainERC20Factory(); + } + + /// @notice Deploy the OptimismSuperchainERC20Beacon predeploy contract + function _deployBeacon() internal { + // Deploy the OptimismSuperchainERC20Beacon implementation + address _addr = Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON; + address _impl = Predeploys.predeployToCodeNamespace(_addr); + vm.etch(_impl, vm.getDeployedCode("OptimismSuperchainERC20Beacon.sol:OptimismSuperchainERC20Beacon")); + + // Deploy the ERC1967Proxy contract at the Predeploy + bytes memory code = vm.getDeployedCode("universal/Proxy.sol:Proxy"); + vm.etch(_addr, code); + EIP1967Helper.setAdmin(_addr, Predeploys.PROXY_ADMIN); + EIP1967Helper.setImplementation(_addr, _impl); + + // Mock implementation address + vm.mockCall( + _impl, abi.encodeWithSelector(IBeacon.implementation.selector), abi.encode(address(superchainERC20Impl)) + ); + } + + /// @notice Test that calling `deploy` with valid parameters succeeds. + function test_deploy_succeeds( + address _caller, + address _remoteToken, + string memory _name, + string memory _symbol, + uint8 _decimals + ) + public + { + // Arrange + bytes32 salt = keccak256(abi.encode(_remoteToken, _name, _symbol, _decimals)); + address deployment = _calculateTokenAddress(salt, address(superchainERC20Factory)); + + vm.expectEmit(true, true, true, true); + emit OptimismSuperchainERC20Factory.OptimismSuperchainERC20Created(deployment, _remoteToken, _caller); + + // Act + vm.prank(_caller); + address addr = superchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals); + + // Assert + assertTrue(addr == deployment); + assertTrue(OptimismSuperchainERC20(deployment).decimals() == _decimals); + assertTrue(OptimismSuperchainERC20(deployment).remoteToken() == _remoteToken); + assertEq(OptimismSuperchainERC20(deployment).name(), _name); + assertEq(OptimismSuperchainERC20(deployment).symbol(), _symbol); + assertEq(superchainERC20Factory.deployments(deployment), _remoteToken); + } + + /// @notice Test that calling `deploy` with the same parameters twice reverts. + function test_deploy_sameTwice_reverts( + address _caller, + address _remoteToken, + string memory _name, + string memory _symbol, + uint8 _decimals + ) + external + { + // Arrange + vm.prank(_caller); + superchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals); + + vm.expectRevert(bytes("DEPLOYMENT_FAILED")); + + // Act + vm.prank(_caller); + superchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals); + } + + /// @notice Precalculates the address of the token contract using CREATE3. + function _calculateTokenAddress(bytes32 _salt, address _deployer) internal pure returns (address) { + address proxy = + keccak256(abi.encodePacked(bytes1(0xFF), _deployer, _salt, CREATE3.PROXY_BYTECODE_HASH)).fromLast20Bytes(); + + return keccak256(abi.encodePacked(hex"d694", proxy, hex"01")).fromLast20Bytes(); + } +} diff --git a/packages/contracts-bedrock/test/L2Genesis.t.sol b/packages/contracts-bedrock/test/L2Genesis.t.sol index 23c457da6a2f..882de4adeb53 100644 --- a/packages/contracts-bedrock/test/L2Genesis.t.sol +++ b/packages/contracts-bedrock/test/L2Genesis.t.sol @@ -150,8 +150,8 @@ contract L2GenesisTest is Test { // 2 predeploys do not have proxies assertEq(getCodeCount(_path, "Proxy.sol:Proxy"), Predeploys.PREDEPLOY_COUNT - 2); - // 22 proxies have the implementation set if useInterop is true and 17 if useInterop is false - assertEq(getPredeployCountWithSlotSet(_path, Constants.PROXY_IMPLEMENTATION_ADDRESS), _useInterop ? 22 : 17); + // 23 proxies have the implementation set if useInterop is true and 17 if useInterop is false + assertEq(getPredeployCountWithSlotSet(_path, Constants.PROXY_IMPLEMENTATION_ADDRESS), _useInterop ? 23 : 17); // All proxies except 2 have the proxy 1967 admin slot set to the proxy admin assertEq( diff --git a/packages/contracts-bedrock/test/setup/Setup.sol b/packages/contracts-bedrock/test/setup/Setup.sol index 5dc12d615afb..856658cfcfd7 100644 --- a/packages/contracts-bedrock/test/setup/Setup.sol +++ b/packages/contracts-bedrock/test/setup/Setup.sol @@ -43,6 +43,7 @@ import { DataAvailabilityChallenge } from "src/L1/DataAvailabilityChallenge.sol" import { WETH } from "src/L2/WETH.sol"; import { SuperchainWETH } from "src/L2/SuperchainWETH.sol"; import { ETHLiquidity } from "src/L2/ETHLiquidity.sol"; +import { IOptimismERC20Factory } from "src/L2/IOptimismERC20Factory.sol"; /// @title Setup /// @dev This contact is responsible for setting up the contracts in state. It currently @@ -99,6 +100,10 @@ contract Setup { SuperchainWETH superchainWeth = SuperchainWETH(payable(Predeploys.SUPERCHAIN_WETH)); ETHLiquidity ethLiquidity = ETHLiquidity(Predeploys.ETH_LIQUIDITY); + // TODO: Replace with OptimismSuperchainERC20Factory when updating pragmas + IOptimismERC20Factory l2OptimismSuperchainERC20Factory = + IOptimismERC20Factory(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY); + /// @dev Deploys the Deploy contract without including its bytecode in the bytecode /// of this contract by fetching the bytecode dynamically using `vm.getCode()`. /// If the Deploy bytecode is included in this contract, then it will double @@ -217,6 +222,8 @@ contract Setup { labelPredeploy(Predeploys.WETH); labelPredeploy(Predeploys.SUPERCHAIN_WETH); labelPredeploy(Predeploys.ETH_LIQUIDITY); + labelPredeploy(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY); + labelPredeploy(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON); // L2 Preinstalls labelPreinstall(Preinstalls.MultiCall3);