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

feat: add constructor to superchain ERC20 beacon #34

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
21 changes: 18 additions & 3 deletions packages/contracts-bedrock/scripts/L2Genesis.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { L1StandardBridge } from "src/L1/L1StandardBridge.sol";
import { FeeVault } from "src/universal/FeeVault.sol";
import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol";
import { Process } from "scripts/libraries/Process.sol";
import { OptimismSuperchainERC20Beacon } from "src/L2/OptimismSuperchainERC20Beacon.sol";

interface IInitializable {
function initialize(address _addr) external;
Expand Down Expand Up @@ -513,10 +514,24 @@ contract L2Genesis is Deployer {
_setImplementationCode(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY);
}

/// @notice This predeploy is following the safety invariant #1.
/// This contract has no initializer.
/// @notice This predeploy is following the safety invariant #2.
function setOptimismSuperchainERC20Beacon() internal {
_setImplementationCode(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON);
// TODO: Precalculate the address of the implementation contract
address superchainERC20Impl = address(uint160(uint256(keccak256("OptimismSuperchainERC20"))));
console.log("Setting %s implementation at: %s", "OptimismSuperchainERC20", superchainERC20Impl);
vm.etch(superchainERC20Impl, vm.getDeployedCode("OptimismSuperchainERC20.sol:OptimismSuperchainERC20"));

OptimismSuperchainERC20Beacon beacon = new OptimismSuperchainERC20Beacon(superchainERC20Impl);
console.log(
"Setting %s implementation at: %s",
"OptimismSuperchainERC20Beacon",
Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON
);
vm.etch(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON, address(beacon).code);

/// Reset so its not included state dump
vm.etch(address(beacon), "");
vm.resetNonce(address(beacon));
}

/// @notice Sets all the preinstalls.
Expand Down
4 changes: 2 additions & 2 deletions packages/contracts-bedrock/semver-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@
"sourceCodeHash": "0x6943d40010dcbd1d51dc3668d0a154fbb1568ea49ebcf3aa039d65ef6eab321b"
},
"src/L2/OptimismSuperchainERC20Beacon.sol": {
"initCodeHash": "0xc9893a1ba9b6354ccbdc96918510c9402a0934a59f0cc4a6165955a8e8ee1f7d",
"sourceCodeHash": "0xd069f4bddd08c1435d286819e1bb765648a5ad861fa327547f52de65a372769e"
"initCodeHash": "0x99ce8095b23c124850d866cbc144fee6cee05dbc6bb5d83acadfe00b90cf42c7",
"sourceCodeHash": "0x00eb41c15cf548dfb6425f317d85262c1edd5f1ad2386a6a76695781d158cf16"
},
"src/L2/OptimismSuperchainERC20Factory.sol": {
"initCodeHash": "0x98011045722178751e4a1112892f7d9a11bc1f5e42ac18205b6d30a1f1476d24",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
[
{
"inputs": [
{
"internalType": "address",
"name": "_implementation",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "implementation",
Expand All @@ -9,7 +20,7 @@
"type": "address"
}
],
"stateMutability": "pure",
"stateMutability": "view",
"type": "function"
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
pragma solidity 0.8.15;

import { IBeacon } from "@openzeppelin/contracts-v5/proxy/beacon/IBeacon.sol";
import { IBeacon } from "@openzeppelin/contracts/proxy/beacon/IBeacon.sol";
import { ISemver } from "src/universal/ISemver.sol";

/// @custom:proxied
/// @custom:predeployed 0x4200000000000000000000000000000000000027
/// @title OptimismSuperchainERC20Beacon
/// @notice OptimismSuperchainERC20Beacon is the beacon proxy for the OptimismSuperchainERC20 implementation.
contract OptimismSuperchainERC20Beacon is IBeacon, ISemver {
/// TODO: Replace with real implementation address
/// @notice Address of the OptimismSuperchainERC20 implementation.
address internal constant IMPLEMENTATION_ADDRESS = 0x0000000000000000000000000000000000000000;
address internal immutable IMPLEMENTATION;

/// @notice Semantic version.
/// @custom:semver 1.0.0-beta.1
string public constant version = "1.0.0-beta.1";

constructor(address _implementation) {
IMPLEMENTATION = _implementation;
}

/// @inheritdoc IBeacon
function implementation() external pure override returns (address) {
return IMPLEMENTATION_ADDRESS;
function implementation() external view override returns (address) {
return IMPLEMENTATION;
}
}
2 changes: 1 addition & 1 deletion packages/contracts-bedrock/src/libraries/Predeploys.sol
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ library Predeploys {

/// @notice Returns true if the predeploy is not proxied.
function notProxied(address _addr) internal pure returns (bool) {
return _addr == GOVERNANCE_TOKEN || _addr == WETH;
return _addr == GOVERNANCE_TOKEN || _addr == WETH || _addr == OPTIMISM_SUPERCHAIN_ERC20_BEACON;
}

/// @notice Returns true if the address is a defined predeploy that is embedded into new OP-Stack chains.
Expand Down
14 changes: 7 additions & 7 deletions packages/contracts-bedrock/test/L2Genesis.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -147,18 +147,18 @@ contract L2GenesisTest is Test {
genesis.setPredeployProxies();
genesis.writeGenesisAllocs(_path);

// 2 predeploys do not have proxies
assertEq(getCodeCount(_path, "Proxy.sol:Proxy"), Predeploys.PREDEPLOY_COUNT - 2);
// 3 predeploys do not have proxies
assertEq(getCodeCount(_path, "Proxy.sol:Proxy"), Predeploys.PREDEPLOY_COUNT - 3);

// 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);
// 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);

// All proxies except 2 have the proxy 1967 admin slot set to the proxy admin
// All proxies except 3 have the proxy 1967 admin slot set to the proxy admin
assertEq(
getPredeployCountWithSlotSetToValue(
_path, Constants.PROXY_OWNER_ADDRESS, bytes32(uint256(uint160(Predeploys.PROXY_ADMIN)))
),
Predeploys.PREDEPLOY_COUNT - 2
Predeploys.PREDEPLOY_COUNT - 3
);

// Also see Predeploys.t.test_predeploysSet_succeeds which uses L1Genesis for the CommonTest prestate.
Expand All @@ -185,7 +185,7 @@ contract L2GenesisTest is Test {
genesis.writeGenesisAllocs(_path);

uint256 expected = 0;
expected += 2048 - 2; // predeploy proxies
expected += 2048 - 3; // predeploy proxies
expected += 21; // predeploy implementations (excl. legacy erc20-style eth and legacy message sender)
expected += 256; // precompiles
expected += 12; // preinstalls
Expand Down
80 changes: 46 additions & 34 deletions packages/contracts-bedrock/test/Predeploys.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,49 +7,39 @@ import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";

/// @title PredeploysTest
contract PredeploysTest is CommonTest {
contract PredeploysBaseTest is CommonTest {
//////////////////////////////////////////////////////
/// Internal helpers
//////////////////////////////////////////////////////

/// @dev Returns true if the address is a predeploy that is active (i.e. embedded in L2 genesis).
function _isPredeploy(address _addr) internal pure returns (bool) {
return _addr == Predeploys.L2_TO_L1_MESSAGE_PASSER || _addr == Predeploys.L2_CROSS_DOMAIN_MESSENGER
|| _addr == Predeploys.L2_STANDARD_BRIDGE || _addr == Predeploys.L2_ERC721_BRIDGE
|| _addr == Predeploys.SEQUENCER_FEE_WALLET || _addr == Predeploys.OPTIMISM_MINTABLE_ERC20_FACTORY
|| _addr == Predeploys.OPTIMISM_MINTABLE_ERC721_FACTORY || _addr == Predeploys.L1_BLOCK_ATTRIBUTES
|| _addr == Predeploys.GAS_PRICE_ORACLE || _addr == Predeploys.DEPLOYER_WHITELIST || _addr == Predeploys.WETH
|| _addr == Predeploys.L1_BLOCK_NUMBER || _addr == Predeploys.LEGACY_MESSAGE_PASSER
|| _addr == Predeploys.PROXY_ADMIN || _addr == Predeploys.BASE_FEE_VAULT || _addr == Predeploys.L1_FEE_VAULT
|| _addr == Predeploys.GOVERNANCE_TOKEN || _addr == Predeploys.SCHEMA_REGISTRY || _addr == Predeploys.EAS;
/// @dev Returns true if the address is an interop predeploy.
function _isInterop(address _addr) internal pure returns (bool) {
return _addr == Predeploys.CROSS_L2_INBOX || _addr == Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER
|| _addr == Predeploys.SUPERCHAIN_WETH || _addr == Predeploys.ETH_LIQUIDITY
|| _addr == Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY || _addr == Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON;
}

/// @dev Returns true if the address is not proxied.
function _notProxied(address _addr) internal pure returns (bool) {
return _addr == Predeploys.GOVERNANCE_TOKEN || _addr == Predeploys.WETH;
/// @dev Returns true if the address is a predeploy that has a different code in the interop mode.
function _interopCodeDiffer(address _addr) internal pure returns (bool) {
return _addr == Predeploys.L1_BLOCK_ATTRIBUTES || _addr == Predeploys.L2_STANDARD_BRIDGE;
}

/// @dev Returns true if the account is not meant to be in the L2 genesis anymore.
function _isOmitted(address _addr) internal pure returns (bool) {
return _addr == Predeploys.L1_MESSAGE_SENDER;
}

/// @dev Returns true if the predeploy is initializable.
function _isInitializable(address _addr) internal pure returns (bool) {
return !(
_addr == Predeploys.LEGACY_MESSAGE_PASSER || _addr == Predeploys.DEPLOYER_WHITELIST
|| _addr == Predeploys.GAS_PRICE_ORACLE || _addr == Predeploys.SEQUENCER_FEE_WALLET
|| _addr == Predeploys.BASE_FEE_VAULT || _addr == Predeploys.L1_FEE_VAULT
|| _addr == Predeploys.L1_BLOCK_NUMBER || _addr == Predeploys.L1_BLOCK_ATTRIBUTES
|| _addr == Predeploys.L2_TO_L1_MESSAGE_PASSER || _addr == Predeploys.OPTIMISM_MINTABLE_ERC721_FACTORY
|| _addr == Predeploys.PROXY_ADMIN || _addr == Predeploys.SCHEMA_REGISTRY || _addr == Predeploys.EAS
|| _addr == Predeploys.GOVERNANCE_TOKEN
);
return _addr == Predeploys.L2_CROSS_DOMAIN_MESSENGER || _addr == Predeploys.L2_STANDARD_BRIDGE
|| _addr == Predeploys.L2_ERC721_BRIDGE || _addr == Predeploys.OPTIMISM_MINTABLE_ERC20_FACTORY;
}

/// @dev Returns true if the predeploy uses immutables.
function _usesImmutables(address _addr) internal pure returns (bool) {
return _addr == Predeploys.OPTIMISM_MINTABLE_ERC721_FACTORY || _addr == Predeploys.SEQUENCER_FEE_WALLET
|| _addr == Predeploys.BASE_FEE_VAULT || _addr == Predeploys.L1_FEE_VAULT || _addr == Predeploys.EAS
|| _addr == Predeploys.GOVERNANCE_TOKEN;
|| _addr == Predeploys.GOVERNANCE_TOKEN || _addr == Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON;
}

function test_predeployToCodeNamespace() external pure {
Expand All @@ -67,33 +57,33 @@ contract PredeploysTest is CommonTest {
);
}

/// @dev Tests that the predeploy addresses are set correctly. They have code
/// and the proxied accounts have the correct admin.
function test_predeploys_succeeds() external {
function _test_predeploys(bool _useInterop) internal {
uint256 count = 2048;
uint160 prefix = uint160(0x420) << 148;

bytes memory proxyCode = vm.getDeployedCode("Proxy.sol:Proxy");

for (uint256 i = 0; i < count; i++) {
address addr = address(prefix | uint160(i));
bytes memory code = addr.code;
assertTrue(code.length > 0);

address implAddr = Predeploys.predeployToCodeNamespace(addr);

if (_isOmitted(addr)) {
assertEq(implAddr.code.length, 0, "must have no code");
continue;
}
bool isPredeploy = _isPredeploy(addr);

bool isPredeploy = Predeploys.isSupportedPredeploy(addr, _useInterop);

bytes memory code = addr.code;
if (isPredeploy) assertTrue(code.length > 0);

bool proxied = Predeploys.notProxied(addr) == false;

if (!isPredeploy) {
// All of the predeploys, even if inactive, have their admin set to the proxy admin
assertEq(EIP1967Helper.getAdmin(addr), Predeploys.PROXY_ADMIN, "Admin mismatch");
if (proxied) assertEq(EIP1967Helper.getAdmin(addr), Predeploys.PROXY_ADMIN, "Admin mismatch");
continue;
}
bool proxied = _notProxied(addr) == false;

string memory cname = Predeploys.getName(addr);
assertNotEq(cname, "", "must have a name");
Expand All @@ -118,7 +108,7 @@ contract PredeploysTest is CommonTest {
string.concat("Implementation mismatch for ", vm.toString(addr))
);
assertNotEq(implAddr.code.length, 0, "predeploy implementation account must have code");
if (!_usesImmutables(addr)) {
if (!_usesImmutables(addr) && !_interopCodeDiffer(addr)) {
// can't check bytecode if it's modified with immutables in genesis.
assertEq(implAddr.code, supposedCode, "proxy implementation contract should match contract source");
}
Expand All @@ -129,3 +119,25 @@ contract PredeploysTest is CommonTest {
}
}
}

contract PredeploysTest is PredeploysBaseTest {
/// @dev Tests that the predeploy addresses are set correctly. They have code
/// and the proxied accounts have the correct admin.
function test_predeploys_succeeds() external {
_test_predeploys(false);
}
}

contract PredeploysInteropTest is PredeploysBaseTest {
/// @notice Test setup. Enabling interop to get all predeploys.
function setUp() public virtual override {
super.enableInterop();
super.setUp();
}

/// @dev Tests that the predeploy addresses are set correctly. They have code
/// and the proxied accounts have the correct admin. Using interop.
function test_predeploys_succeeds() external {
_test_predeploys(true);
}
}