Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: add superchain erc20 factory tests #25

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions packages/contracts-bedrock/.gas-snapshot
Original file line number Diff line number Diff line change
@@ -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)
GasBenchMark_OptimismPortal:test_proveWithdrawalTransaction_benchmark() (gas: 155615)
4 changes: 4 additions & 0 deletions packages/contracts-bedrock/scripts/Artifacts.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down
14 changes: 14 additions & 0 deletions packages/contracts-bedrock/scripts/L2Genesis.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,8 @@ contract L2Genesis is Deployer {
setL2ToL2CrossDomainMessenger(); // 23
setSuperchainWETH(); // 24
setETHLiquidity(); // 25
setOptimismSuperchainERC20Factory(); // 26
setOptimismSuperchainERC20Beacon(); // 27
}
}

Expand Down Expand Up @@ -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,
Expand Down
15 changes: 6 additions & 9 deletions packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did you remove the Predeploys... way of calling it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to replicate the way they use the initialized predeploys across the different test suites


// Expect the revert with `InvalidSuperchainERC20Address` selector
vm.expectRevert(InvalidSuperchainERC20Address.selector);
Expand Down Expand Up @@ -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);
Expand All @@ -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));
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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));
Expand Down
32 changes: 28 additions & 4 deletions packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand All @@ -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))
)
)
Expand Down
Original file line number Diff line number Diff line change
@@ -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);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can also assert for name symbol and remote token

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();
}
}
4 changes: 2 additions & 2 deletions packages/contracts-bedrock/test/L2Genesis.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
7 changes: 7 additions & 0 deletions packages/contracts-bedrock/test/setup/Setup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down