Skip to content

Commit

Permalink
test: add superchain erc20 factory tests (#25)
Browse files Browse the repository at this point in the history
* test: add superchain erc20 factory tests

* test: add erc20 asserts
  • Loading branch information
agusduha authored and 0xng committed Sep 11, 2024
1 parent a086b5d commit 464799e
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 14 deletions.
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 @@ -154,6 +154,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 @@ -19,9 +19,6 @@ import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol
import { IOptimismMintableERC20 } from "src/universal/interfaces/IOptimismMintableERC20.sol";
import { ILegacyMintableERC20 } from "src/universal/OptimismMintableERC20.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 @@ -146,7 +143,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);
Expand Down Expand Up @@ -177,7 +174,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 @@ -204,7 +201,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(address(l2StandardBridge));
Expand Down Expand Up @@ -300,7 +297,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 @@ -331,7 +328,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 @@ -358,7 +355,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(address(l2StandardBridge));
Expand Down
31 changes: 28 additions & 3 deletions packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ 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";
Expand All @@ -11,6 +12,8 @@ import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomai
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 +43,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 +81,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);
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
6 changes: 6 additions & 0 deletions packages/contracts-bedrock/test/setup/Setup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,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 @@ -226,6 +230,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

0 comments on commit 464799e

Please sign in to comment.