2.0 : Prerequisites

Make sure you have run the deploy script for governance layer:

You should have following file with below fields saved at the deployments/31337/.save.json. Otherwise, as specified in .env.<network>.local.

{
"SafeProxyFactory": "<ADDRESS_1>",
"SafeSingleton": "<ADDRESS_2>",
"SystemOwnerSafe": "<ADDRESS_3>"
}

Without this artifact file, the next deployment scripts can not be run.

2.1A : Deploy AddressManager Contract

In your terminal, copy below contracts' codes and run deployment scripts to your prefered network:

forge script script/201A_DeployAddressManagerScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast

(Optional), you can specify your derivation path:

--mnemonic-derivation-paths "m/44'/60'/0'/0/0"

Contract Settings

Smart Contract:
AddressManager.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Ownable} from "@redprint-openzeppelin/access/Ownable.sol";

/// @custom:security-contact Consult full code at https://github.com/ethereum-optimism/optimism/blob/v1.9.0/packages/contracts-bedrock/src/legacy/AddressManager.sol
contract AddressManager is Ownable {
    mapping(bytes32 => address) private addresses;
    event AddressSet(string indexed name, address newAddress, address oldAddress);

    function setAddress(string memory _name, with _address) external whenNotPaused {
        bytes32 nameHash = _getNameHash(_name);
        address oldAddress = addresses[nameHash];
        addresses[nameHash] = _address;
        emit AddressSet(_name, _address, oldAddress);
    }

    function getAddress(string memory _name) external view returns (address) {
        return addresses[_getNameHash(_name)];
    }

    function _getNameHash(string memory _name) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked(_name));
    }
}

        
      
Deploy Script:
201A_DeployAddressManagerScript.s.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {DeployScript} from "@redprint-deploy/deployer/DeployScript.sol";
import {DeployerFunctions, IDeployer} from "@redprint-deploy/deployer/DeployerFunctions.sol";
import {AddressManager} from "@redprint-core/legacy/AddressManager.sol";

/// @custom:security-contact Consult full internal deploy script at https://github.com/Ratimon/redprint-forge
contract DeployAddressManagerScript is DeployScript {
    using DeployerFunctions for IDeployer ;
    function deploy() external returns (AddressManager) {
        return AddressManager(deployer.deploy_AddressManager("AddressManager"));
    }
}

        
      

After running the deploy script, the address deployed is saved at deployments/31337/.save.json. Otherwise, as specified in .env.<network>.local.

2.1B : Deploy And Setup ProxyAdmin Contract

In your terminal, copy below contracts' codes and run deployment scripts to your prefered network:

forge script script/201B_DeployAndSetupProxyAdminScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast

(Optional), you can specify your derivation path:

--mnemonic-derivation-paths "m/44'/60'/0'/0/0"

Contract Settings

OpSec Management

Owner

Smart Contract:
ProxyAdmin.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Ownable} from "@redprint-openzeppelin/access/Ownable.sol";
import {IStaticERC1967Proxy, IStaticL1ChugSplashProxy} from "@redprint-core/universal/ProxyAdmin.sol";
import {Proxy} from "@redprint-core/universal/Proxy.sol";
import {AddressManager} from "@redprint-core/legacy/AddressManager.sol";
import {L1ChugSplashProxy} from "@redprint-core/legacy/L1ChugSplashProxy.sol";
import {Constants} from "@redprint-core/libraries/Constants.sol";

/// @custom:security-contact Consult full code at https://github.com/ethereum-optimism/optimism/blob/v1.9.0/packages/contracts-bedrock/src/universal/ProxyAdmin.sol
contract ProxyAdmin is Ownable {
    enum ProxyType {
        ERC1967,
        CHUGSPLASH,
        RESOLVED
    }
    mapping(address => ProxyType) public proxyType;
    mapping(address => string) public implementationName;
    AddressManager public addressManager;
    bool internal upgrading;

    constructor(address _owner) {
        _transferOwnership(_owner);
    }

    function setProxyType(address _address, ProxyType _type) external onlyOwner {
        proxyType[_address] = _type;
    }

    function setImplementationName(address _address, string memory _name)
        external
        onlyOwner
    {
        implementationName[_address] = _name;
    }

    function setAddressManager(AddressManager _address) external onlyOwner {
        addressManager = _address;
    }

    function setAddress(string memory _name, address _address) external onlyOwner {
        addressManager.setAddress(_name, _address);
    }

    function setUpgrading(bool _upgrading) external onlyOwner {
        upgrading = _upgrading;
    }

    function isUpgrading() external view returns (bool) {
        return upgrading;
    }

    function getProxyImplementation(address _proxy)
        external
        view
        returns (address)
    {
        ProxyType ptype = proxyType[_proxy];
        if (ptype == ProxyType.ERC1967) {
            return IStaticERC1967Proxy(_proxy).implementation();
        } else if (ptype == ProxyType.CHUGSPLASH) {
            return IStaticL1ChugSplashProxy(_proxy).getImplementation();
        } else if (ptype == ProxyType.RESOLVED) {
            return addressManager.getAddress(implementationName[_proxy]);
        } else {
            revert("ProxyAdmin: unknown proxy type");
        }
    }

    function getProxyAdmin(address payable _proxy)
        external
        view
        returns (address)
    {
        ProxyType ptype = proxyType[_proxy];
      if (ptype == ProxyType.ERC1967) {
          return IStaticERC1967Proxy(_proxy).admin();
      } else if (ptype == ProxyType.CHUGSPLASH) {
          return IStaticL1ChugSplashProxy(_proxy).getOwner();
      } else if (ptype == ProxyType.RESOLVED) {
          return addressManager.owner();
      } else {
          revert("ProxyAdmin: unknown proxy type");
      }
    }

    function changeProxyAdmin(address payable _proxy, address _newAdmin)
        external
        onlyOwner
    {
        ProxyType ptype = proxyType[_proxy];
        if (ptype == ProxyType.ERC1967) {
            Proxy(_proxy).changeAdmin(_newAdmin);
        } else if (ptype == ProxyType.CHUGSPLASH) {
            L1ChugSplashProxy(_proxy).setOwner(_newAdmin);
        } else if (ptype == ProxyType.RESOLVED) {
            addressManager.transferOwnership(_newAdmin);
        } else {
            revert("ProxyAdmin: unknown proxy type");
        }
    }

    function upgrade(address payable _proxy, address _implementation)
        public
        onlyOwner
    {
        ProxyType ptype = proxyType[_proxy];
        if (ptype == ProxyType.ERC1967) {
            Proxy(_proxy).upgradeTo(_implementation);
        } else if (ptype == ProxyType.CHUGSPLASH) {
            L1ChugSplashProxy(_proxy).setStorage(
                Constants.PROXY_IMPLEMENTATION_ADDRESS, bytes32(uint256(uint160(_implementation)))
            );
        } else if (ptype == ProxyType.RESOLVED) {
            string memory name = implementationName[_proxy];
            addressManager.setAddress(name, _implementation);
        } else {
            // It should not be possible to retrieve a ProxyType value which is not matched by
            // one of the previous conditions.
            assert(false);
        }
    }

    function upgradeAndCall(address payable _proxy, address _implementation, bytes memory _data)
        external payable
        onlyOwner
    {
        ProxyType ptype = proxyType[_proxy];
        if (ptype == ProxyType.ERC1967) {
            Proxy(_proxy).upgradeToAndCall{value: msg.value}(_implementation, _data);
        } else {
            // reverts if proxy type is unknown
            upgrade(_proxy, _implementation);
            (bool success,) = _proxy.call{value: msg.value}(_data);
            require(success, "ProxyAdmin: call to proxy after upgrade failed");
        }
    }
}

        
      
Deploy Script:
201B_DeployAndSetupProxyAdminScript.s.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {DeployScript} from "@redprint-deploy/deployer/DeployScript.sol";
import {DeployerFunctions, IDeployer} from "@redprint-deploy/deployer/DeployerFunctions.sol";
import {console} from "@redprint-forge-std/console.sol";
import {Vm, VmSafe} from "@redprint-forge-std/Vm.sol";
import {AddressManager} from "@redprint-core/legacy/AddressManager.sol";
import {ProxyAdmin} from "@redprint-core/universal/ProxyAdmin.sol";

/// @custom:security-contact Consult full internal deploy script at https://github.com/Ratimon/redprint-forge
contract DeployAndSetupProxyAdminScript is DeployScript {
    using DeployerFunctions for IDeployer ;
    ProxyAdmin proxyAdmin;
    string mnemonic = vm.envString("MNEMONIC");
    uint256 ownerPrivateKey = vm.deriveKey(mnemonic, "m/44'/60'/0'/0/", 1);
    address owner = vm.envOr("DEPLOYER_ADDRESS", vm.addr(ownerPrivateKey));

    function deploy() external returns (ProxyAdmin) {
        proxyAdmin = deployer.deploy_ProxyAdmin("ProxyAdmin", address(owner));
        require(proxyAdmin.owner() == address(owner));
        return proxyAdmin;
    }

    function initialize() external {
        AddressManager addressManager = AddressManager(deployer.mustGetAddress("AddressManager"));
        (VmSafe.CallerMode mode ,address msgSender, ) = vm.readCallers();
        if (proxyAdmin.addressManager() != addressManager) {
             if(mode != VmSafe.CallerMode.Broadcast && msgSender != owner) {
                console.log("Pranking ower ...");
                vm.prank(owner);
             } else {
                console.log("Broadcasting ...");
                vm.broadcast(owner);
             }
            proxyAdmin.setAddressManager(addressManager);
            console.log("AddressManager setted to : %s", address(addressManager));
        }
        address safe = deployer.mustGetAddress("SystemOwnerSafe");
        if (proxyAdmin.owner() != safe) {
            if(mode != VmSafe.CallerMode.Broadcast && msgSender != owner) {
                console.log("Pranking ower ...");
                vm.prank(owner);
             } else {
                console.log("Broadcasting ...");
                vm.broadcast(owner);
             }

            proxyAdmin.transferOwnership(safe);
            console.log("ProxyAdmin ownership transferred to Safe at: %s", safe);
        }
    }
}

        
      

After running the deploy script, the address deployed is saved at deployments/31337/.save.json. Otherwise, as specified in .env.<network>.local.

2.2A : Deploy SuperchainConfigProxy Contract

In your terminal, copy below contracts' codes and run deployment scripts to your prefered network:

forge script script/202A_DeploySuperchainConfigProxyScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast

(Optional), you can specify your derivation path:

--mnemonic-derivation-paths "m/44'/60'/0'/0/0"

Contract Settings

Smart Contract:
Proxy.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/// @custom:security-contact Consult full code at https://github.com/ethereum-optimism/optimism/blob/v1.9.0/packages/contracts-bedrock/src/universal/Proxy.sol
contract Proxy {
    /**
     * @notice The storage slot that holds the address of the implementation.
     *         bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)
     */
    bytes32 internal constant IMPLEMENTATION_KEY = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;}
    /**
     * @notice The storage slot that holds the address of the owner.
     *         bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1)
     */
    bytes32 internal constant OWNER_KEY = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
    /**
     * @notice An event that is emitted each time the implementation is changed. This event is part
     *         of the EIP-1967 specification.
     *
     * @param implementation The address of the implementation contract
     */
    event Upgraded(address indexed implementation);
    /**
     * @notice An event that is emitted each time the owner is upgraded. This event is part of the
     *         EIP-1967 specification.
     *
     * @param previousAdmin The previous owner of the contract
     * @param newAdmin      The new owner of the contract
     */
    event AdminChanged(address previousAdmin, address newAdmin);

    modifier proxyCallIfNotAdmin() {
        if (msg.sender == _getAdmin() || msg.sender == address(0)) {
            _;
        } else {
            // This WILL halt the call frame on completion.
            _doProxyCall();
        }
    }

    constructor(address _admin) {
        _changeAdmin(_admin);
    }

    receive() external payable {
        // Proxy call by default.
        _doProxyCall();
    }

    fallback() external payable {
        // Proxy call by default.
        _doProxyCall();
    }

    function upgradeTo(address _implementation)
        public
        virtual
        returns (proxyCallIfNotAdmin)
    {
        _setImplementation(_implementation);
    }

    function upgradeToAndCall(address _implementation, bytes calldata _data)
        public
        payable virtual proxyCallIfNotAdmin
        returns (bytes memory)
    {
        _setImplementation(_implementation);
        (bool success, bytes memory returndata) = _implementation.delegatecall(_data);
        require(success, "Proxy: delegatecall to new implementation contract failed");
        return returndata;
    }

    function changeAdmin(address _admin) public virtual proxyCallIfNotAdmin {
        _changeAdmin(_admin);
    }

    function admin() public virtual proxyCallIfNotAdmin returns (address) {
        return _getAdmin();
    }

    function implementation()
        public
        virtual proxyCallIfNotAdmin
        returns (address)
    {
        return _getImplementation();
    }

    function _setImplementation(address _implementation) internal {
        assembly {
            sstore(IMPLEMENTATION_KEY, _implementation)
        }
        emit Upgraded(_implementation);
    }

    function _changeAdmin(address _admin) internal {
        address previous = _getAdmin();
        assembly {
            sstore(OWNER_KEY, _admin)
        }
        emit AdminChanged(previous, _admin);
    }

    function _doProxyCall() internal {
        address impl = _getImplementation();
        require(impl != address(0), "Proxy: implementation not initialized");

        assembly {
            // Copy calldata into memory at 0x0....calldatasize.
            calldatacopy(0x0, 0x0, calldatasize())

            // Perform the delegatecall, make sure to pass all available gas.
            let success := delegatecall(gas(), impl, 0x0, calldatasize(), 0x0, 0x0)

            // Copy returndata into memory at 0x0....returndatasize. Note that this *will*
            // overwrite the calldata that we just copied into memory but that doesn't really
            // matter because we'll be returning in a second anyway.
            returndatacopy(0x0, 0x0, returndatasize())

            // Success == 0 means a revert. We'll revert too and pass the data up.
            if iszero(success) { revert(0x0, returndatasize()) }

            // Otherwise we'll just return and pass the data up.
            return(0x0, returndatasize())
        }
    }

    function _getImplementation() internal view returns (address) {
        address impl;
        assembly {
            impl := sload(IMPLEMENTATION_KEY)
        }
        return impl;
    }

    function _getAdmin() internal view returns (address) {
        address owner;
        assembly {
            owner := sload(OWNER_KEY)
        }
        return owner;
    }
}

        
      
Deploy Script:
202A_DeploySuperchainConfigProxyScript.s.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {DeployScript} from "@redprint-deploy/deployer/DeployScript.sol";
import {DeployerFunctions, IDeployer} from "@redprint-deploy/deployer/DeployerFunctions.sol";
import {Proxy} from "@redprint-core/universal/Proxy.sol";

/// @custom:security-contact Consult full internal deploy script at https://github.com/Ratimon/redprint-forge
contract DeploySuperchainConfigProxyScript is DeployScript {
    using DeployerFunctions for IDeployer ;
    function deploy() external returns (Proxy) {
        address proxyOwner = deployer.mustGetAddress("ProxyAdmin");

        return Proxy(deployer.deploy_ERC1967Proxy("SuperchainConfigProxy", proxyOwner));
    }
}

        
      

After running the deploy script, the address deployed is saved at deployments/31337/.save.json. Otherwise, as specified in .env.<network>.local.

2.2B : DeployAndInitializeSuperchainConfig Contract

In your terminal, copy below contracts' codes and run deployment scripts to your prefered network:

forge script script/202B_DeployAndInitializeSuperchainConfigScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast

(Optional), you can specify your derivation path:

--mnemonic-derivation-paths "m/44'/60'/0'/0/0"

Contract Settings

Smart Contract:
SuperchainConfig.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Initializable} from "@redprint-openzeppelin/proxy/utils/Initializable.sol";
import {ISemver} from "@redprint-core/universal/ISemver.sol";
import {Storage} from "@redprint-core/libraries/Storage.sol";

/// @custom:security-contact Consult full code at https://github.com/Ratimon/redprint-optimism-contracts-examples/blob/main/src/L1/SuperchainConfig.sol
contract SuperchainConfig is Initializable, ISemver {
    /// @notice Enum representing different types of updates.
    /// @custom:value GUARDIAN            Represents an update to the guardian.
    enum UpdateType {
        GUARDIAN
    }
    /// @notice Whether or not the Superchain is paused.
    bytes32 public constant PAUSED_SLOT = bytes32(uint256(keccak256("superchainConfig.paused")) - 1);
    /// @notice The address of the guardian, which can pause withdrawals from the System.
    ///         It can only be modified by an upgrade.
    bytes32 public constant GUARDIAN_SLOT = bytes32(uint256(keccak256("superchainConfig.guardian")) - 1);
    /// @notice Emitted when the pause is triggered.
    /// @param identifier A string helping to identify provenance of the pause transaction.
    event Paused(string identifier);
    /// @notice Emitted when the pause is lifted.
    event Unpaused();
    event ConfigUpdate(UpdateType indexed updateType, bytes data);
    /// @notice Semantic version.
    /// @custom:semver 1.1.0
    string public constant version = "1.1.0";

    constructor() {
        initialize({_guardian: address(0), _paused: false});
    }

    function initialize(address _guardian, bool _paused) public initializer {
        _setGuardian(_guardian);
        if (_paused) {
            _pause("Initializer paused");
        }
    }

    function guardian() public view returns (address guardian_) {
        guardian_ = Storage.getAddress(GUARDIAN_SLOT);
    }

    function paused() public view returns (bool paused_) {
        paused_ = Storage.getBool(PAUSED_SLOT);
    }

    function pause(string memory _identifier) external {
        require(msg.sender == guardian(), "SuperchainConfig: only guardian can pause");
        _pause(_identifier);
    }

    function _pause(string memory _identifier) internal returns (address) {
        Storage.setBool(PAUSED_SLOT, true);
        emit Paused(_identifier);
    }

    function unpause() external {
        require(msg.sender == guardian(), "SuperchainConfig: only guardian can unpause");
        Storage.setBool(PAUSED_SLOT, false);
        emit Unpaused();
    }

    function _setGuardian(address _guardian) internal {
        Storage.setAddress(GUARDIAN_SLOT, _guardian);
        emit ConfigUpdate(UpdateType.GUARDIAN, abi.encode(_guardian));
    }
}

        
      
Deploy Script:
202B_DeployAndInitializeSuperchainConfigScript.s.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {DeployScript, IDeployer} from "@redprint-deploy/deployer/DeployScript.sol";
import {SafeScript} from "@redprint-deploy/safe-management/SafeScript.sol";
import {DeployerFunctions, DeployOptions} from "@redprint-deploy/deployer/DeployerFunctions.sol";
import {console} from "@redprint-forge-std/console.sol";
import {Vm, VmSafe} from "@redprint-forge-std/Vm.sol";
import {ChainAssertions} from "@redprint-deploy/optimism/ChainAssertions.sol";
import {Proxy} from "@redprint-core/universal/Proxy.sol";
import {SuperchainConfig} from "@redprint-core/L1/SuperchainConfig.sol";

/// @custom:security-contact Consult full internal deploy script at https://github.com/Ratimon/redprint-forge
contract DeployAndInitializeSuperchainConfigScript is DeployScript, SafeScript {
    using DeployerFunctions for IDeployer ;
    SuperchainConfig superchainConfig;
    string mnemonic = vm.envString("MNEMONIC");
    uint256 ownerPrivateKey = vm.deriveKey(mnemonic, "m/44'/60'/0'/0/", 1);
    address owner = vm.envOr("DEPLOYER_ADDRESS", vm.addr(ownerPrivateKey));

    function deploy() external returns (SuperchainConfig) {
        bytes32 _salt = DeployScript.implSalt();

        DeployOptions memory options = DeployOptions({salt:_salt});

        superchainConfig = deployer.deploy_SuperchainConfig("SuperchainConfig", options);
        return superchainConfig;
    }

    function initialize() external {
        (VmSafe.CallerMode mode ,address msgSender, ) = vm.readCallers();
        if(mode != VmSafe.CallerMode.Broadcast && msgSender != owner) {
            console.log("Pranking owner ...");
            vm.startPrank(owner);
            initializeSuperchainConfig();
            vm.stopPrank();
        } else {
            console.log("Broadcasting ...");
            vm.startBroadcast(owner);

            initializeSuperchainConfig();
            console.log("SuperchainConfig setted to : %s", address(superchainConfig));

            vm.stopBroadcast();
        }
    }

    function initializeSuperchainConfig() public {
        console.log("Upgrading and initializing SuperchainConfig");

        address payable superchainConfigProxy = deployer.mustGetAddress("SuperchainConfigProxy");
        address proxyAdmin = deployer.mustGetAddress("ProxyAdmin");
        address safe = deployer.mustGetAddress("SystemOwnerSafe");

        _upgradeAndCallViaSafe({
            _proxyAdmin: proxyAdmin,
            _safe: safe,
            _owner: owner,
            _proxy: superchainConfigProxy,
            _implementation:  address(superchainConfig),
            _innerCallData: abi.encodeCall(SuperchainConfig.initialize, ( deployer.getConfig().superchainConfigGuardian(), false))
        });

        ChainAssertions.checkSuperchainConfig({ _contracts: deployer.getProxiesUnstrict(), _cfg: deployer.getConfig(), _isPaused: false });
    }
}

        
      

After running the deploy script, the address deployed is saved at deployments/31337/.save.json. Otherwise, as specified in .env.<network>.local.

2.3A : Deploy ProtocolVersionsProxy Contract

In your terminal, copy below contracts' codes and run deployment scripts to your prefered network:

forge script script/203A_DeployProtocolVersionsProxyScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast

(Optional), you can specify your derivation path:

--mnemonic-derivation-paths "m/44'/60'/0'/0/0"

Contract Settings

Smart Contract:
Proxy.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/// @custom:security-contact Consult full code at https://github.com/ethereum-optimism/optimism/blob/v1.9.0/packages/contracts-bedrock/src/universal/Proxy.sol
contract Proxy {
    /**
     * @notice The storage slot that holds the address of the implementation.
     *         bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)
     */
    bytes32 internal constant IMPLEMENTATION_KEY = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;}
    /**
     * @notice The storage slot that holds the address of the owner.
     *         bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1)
     */
    bytes32 internal constant OWNER_KEY = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
    /**
     * @notice An event that is emitted each time the implementation is changed. This event is part
     *         of the EIP-1967 specification.
     *
     * @param implementation The address of the implementation contract
     */
    event Upgraded(address indexed implementation);
    /**
     * @notice An event that is emitted each time the owner is upgraded. This event is part of the
     *         EIP-1967 specification.
     *
     * @param previousAdmin The previous owner of the contract
     * @param newAdmin      The new owner of the contract
     */
    event AdminChanged(address previousAdmin, address newAdmin);

    modifier proxyCallIfNotAdmin() {
        if (msg.sender == _getAdmin() || msg.sender == address(0)) {
            _;
        } else {
            // This WILL halt the call frame on completion.
            _doProxyCall();
        }
    }

    constructor(address _admin) {
        _changeAdmin(_admin);
    }

    receive() external payable {
        // Proxy call by default.
        _doProxyCall();
    }

    fallback() external payable {
        // Proxy call by default.
        _doProxyCall();
    }

    function upgradeTo(address _implementation)
        public
        virtual
        returns (proxyCallIfNotAdmin)
    {
        _setImplementation(_implementation);
    }

    function upgradeToAndCall(address _implementation, bytes calldata _data)
        public
        payable virtual proxyCallIfNotAdmin
        returns (bytes memory)
    {
        _setImplementation(_implementation);
        (bool success, bytes memory returndata) = _implementation.delegatecall(_data);
        require(success, "Proxy: delegatecall to new implementation contract failed");
        return returndata;
    }

    function changeAdmin(address _admin) public virtual proxyCallIfNotAdmin {
        _changeAdmin(_admin);
    }

    function admin() public virtual proxyCallIfNotAdmin returns (address) {
        return _getAdmin();
    }

    function implementation()
        public
        virtual proxyCallIfNotAdmin
        returns (address)
    {
        return _getImplementation();
    }

    function _setImplementation(address _implementation) internal {
        assembly {
            sstore(IMPLEMENTATION_KEY, _implementation)
        }
        emit Upgraded(_implementation);
    }

    function _changeAdmin(address _admin) internal {
        address previous = _getAdmin();
        assembly {
            sstore(OWNER_KEY, _admin)
        }
        emit AdminChanged(previous, _admin);
    }

    function _doProxyCall() internal {
        address impl = _getImplementation();
        require(impl != address(0), "Proxy: implementation not initialized");

        assembly {
            // Copy calldata into memory at 0x0....calldatasize.
            calldatacopy(0x0, 0x0, calldatasize())

            // Perform the delegatecall, make sure to pass all available gas.
            let success := delegatecall(gas(), impl, 0x0, calldatasize(), 0x0, 0x0)

            // Copy returndata into memory at 0x0....returndatasize. Note that this *will*
            // overwrite the calldata that we just copied into memory but that doesn't really
            // matter because we'll be returning in a second anyway.
            returndatacopy(0x0, 0x0, returndatasize())

            // Success == 0 means a revert. We'll revert too and pass the data up.
            if iszero(success) { revert(0x0, returndatasize()) }

            // Otherwise we'll just return and pass the data up.
            return(0x0, returndatasize())
        }
    }

    function _getImplementation() internal view returns (address) {
        address impl;
        assembly {
            impl := sload(IMPLEMENTATION_KEY)
        }
        return impl;
    }

    function _getAdmin() internal view returns (address) {
        address owner;
        assembly {
            owner := sload(OWNER_KEY)
        }
        return owner;
    }
}

        
      
Deploy Script:
203A_DeployProtocolVersionsProxyScript.s.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {DeployScript} from "@redprint-deploy/deployer/DeployScript.sol";
import {DeployerFunctions, IDeployer} from "@redprint-deploy/deployer/DeployerFunctions.sol";
import {Proxy} from "@redprint-core/universal/Proxy.sol";

/// @custom:security-contact Consult full internal deploy script at https://github.com/Ratimon/redprint-forge
contract DeployProtocolVersionsProxyScript is DeployScript {
    using DeployerFunctions for IDeployer ;
    function deploy() external returns (Proxy) {
        address proxyOwner = deployer.mustGetAddress("ProxyAdmin");

        return Proxy(deployer.deploy_ERC1967Proxy("ProtocolVersionsProxy", proxyOwner));
    }
}

        
      

After running the deploy script, the address deployed is saved at deployments/31337/.save.json. Otherwise, as specified in .env.<network>.local.

2.3B : DeployAndInitializeProtocolVersions Contract

In your terminal, copy below contracts' codes and run deployment scripts to your prefered network:

forge script script/203B_DeployAndInitializeProtocolVersionsScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast

(Optional), you can specify your derivation path:

--mnemonic-derivation-paths "m/44'/60'/0'/0/0"

Contract Settings

Smart Contract:
ProtocolVersions.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {OwnableUpgradeable} from "@redprint-openzeppelin-upgradable/access/OwnableUpgradeable.sol";
import {ISemver} from "@redprint-core/universal/ISemver.sol";
import {Storage} from "@redprint-core/libraries/Storage.sol";
import {Constants} from "@redprint-core/libraries/Constants.sol";

type ProtocolVersion for uint256;

/// @custom:security-contact Consult full code at https://github.com/Ratimon/redprint-optimism-contracts-examples/blob/main/src/L1/ProtocolVersions.sol
contract ProtocolVersions is OwnableUpgradeable, ISemver {
    /// @notice Enum representing different types of updates.
    /// @custom:value REQUIRED_PROTOCOL_VERSION              Represents an update to the required protocol version.
    /// @custom:value RECOMMENDED_PROTOCOL_VERSION           Represents an update to the recommended protocol version.
    enum UpdateType {
        REQUIRED_PROTOCOL_VERSION,
        RECOMMENDED_PROTOCOL_VERSION
    }
     /// @notice Version identifier, used for upgrades.
    uint256 public constant VERSION = 0;
    /// @notice Storage slot that the required protocol version is stored at.
    bytes32 public constant REQUIRED_SLOT = bytes32(uint256(keccak256("protocolversion.required")) - 1);
    /// @notice Storage slot that the recommended protocol version is stored at.
    bytes32 public constant RECOMMENDED_SLOT = bytes32(uint256(keccak256("protocolversion.recommended")) - 1);
    /// @notice Emitted when configuration is updated.
    /// @param version    ProtocolVersion version.
    /// @param updateType Type of update.
    /// @param data       Encoded update data.
    event ConfigUpdate(uint256 indexed version, UpdateType indexed updateType, bytes data);
    /// @notice Semantic version.
    /// @custom:semver 1.0.0
    string public constant version = "1.0.0";

    constructor() {
        initialize({
            _owner: address(0xdEaD),
            _required: ProtocolVersion.wrap(uint256(0)),
            _recommended: ProtocolVersion.wrap(uint256(0))
        });
    }

    function initialize(address _owner, ProtocolVersion _required, ProtocolVersion _recommended)
        public
        initializer
    {
        __Ownable_init();
        transferOwnership(_owner);
        _setRequired(_required);
        _setRecommended(_recommended);
    }

    function required() external view returns (ProtocolVersion out_) {
        out_ = ProtocolVersion.wrap(Storage.getUint(REQUIRED_SLOT));
    }

    function setRequired(ProtocolVersion _required) external onlyOwner {
        _setRequired(_required);
    }

    function _setRequired(ProtocolVersion _required) internal {
        Storage.setUint(REQUIRED_SLOT, ProtocolVersion.unwrap(_required));
        bytes memory data = abi.encode(_required);
        emit ConfigUpdate(VERSION, UpdateType.REQUIRED_PROTOCOL_VERSION, data);
    }

    function recommended() external view returns (ProtocolVersion out_) {
        out_ = ProtocolVersion.wrap(Storage.getUint(RECOMMENDED_SLOT));
    }

    function setRecommended(ProtocolVersion _recommended) external onlyOwner {
        _setRecommended(_recommended);
    }

    function _setRecommended(ProtocolVersion _recommended) internal {
        Storage.setUint(RECOMMENDED_SLOT, ProtocolVersion.unwrap(_recommended));

        bytes memory data = abi.encode(_recommended);
        emit ConfigUpdate(VERSION, UpdateType.RECOMMENDED_PROTOCOL_VERSION, data);
    }
}

        
      
Deploy Script:
203B_DeployAndInitializeProtocolVersionsScript.s.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {DeployScript, IDeployer} from "@redprint-deploy/deployer/DeployScript.sol";
import {SafeScript} from "@redprint-deploy/safe-management/SafeScript.sol";
import {DeployerFunctions, DeployOptions} from "@redprint-deploy/deployer/DeployerFunctions.sol";
import {console} from "@redprint-forge-std/console.sol";
import {Vm, VmSafe} from "@redprint-forge-std/Vm.sol";
import {Types} from "@redprint-deploy/optimism/Types.sol";
import {ChainAssertions} from "@redprint-deploy/optimism/ChainAssertions.sol";
import {ProtocolVersions, ProtocolVersion} from "@redprint-core/L1/ProtocolVersions.sol";

/// @custom:security-contact Consult full internal deploy script at https://github.com/Ratimon/redprint-forge
contract DeployAndInitializeProtocolVersionsScript is DeployScript, SafeScript {
    using DeployerFunctions for IDeployer ;
    ProtocolVersions versions;
    string mnemonic = vm.envString("MNEMONIC");
    uint256 ownerPrivateKey = vm.deriveKey(mnemonic, "m/44'/60'/0'/0/", 1);
    address owner = vm.envOr("DEPLOYER_ADDRESS", vm.addr(ownerPrivateKey));

    function deploy() external returns (ProtocolVersions) {
        bytes32 _salt = DeployScript.implSalt();

        DeployOptions memory options = DeployOptions({salt:_salt});

        versions = deployer.deploy_ProtocolVersions("ProtocolVersions", options);

        Types.ContractSet memory contracts = _proxiesUnstrict();
        contracts.ProtocolVersions = address(versions);
        ChainAssertions.checkProtocolVersions({ _contracts: contracts, _cfg: deployer.getConfig(), _isProxy: false });

        return versions;
    }

    function initialize() external {
        (VmSafe.CallerMode mode ,address msgSender, ) = vm.readCallers();
        if(mode != VmSafe.CallerMode.Broadcast && msgSender != owner) {
            console.log("Pranking owner ...");
            vm.startPrank(owner);
            initializeProtocolVersions();
            vm.stopPrank();
        } else {
            console.log("Broadcasting ...");
            vm.startBroadcast(owner);

            initializeProtocolVersions();
            console.log("ProtocolVersions setted to : %s", address(versions));

            vm.stopBroadcast();
        }
    }

    function initializeProtocolVersions() public {
        console.log("Upgrading and initializing ProtocolVersions proxy");

        address proxyAdmin = deployer.mustGetAddress("ProxyAdmin");
        address safe = deployer.mustGetAddress("SystemOwnerSafe");

        address protocolVersionsProxy = deployer.mustGetAddress("ProtocolVersionsProxy");
        address protocolVersions = deployer.mustGetAddress("ProtocolVersions");

        address finalSystemOwner = deployer.getConfig().finalSystemOwner();
        uint256 requiredProtocolVersion = deployer.getConfig().requiredProtocolVersion();
        uint256 recommendedProtocolVersion = deployer.getConfig().recommendedProtocolVersion();

        _upgradeAndCallViaSafe({
            _proxyAdmin: proxyAdmin,
            _safe: safe,
            _owner: owner,
            _proxy: payable(protocolVersionsProxy),
            _implementation: protocolVersions,
            _innerCallData: abi.encodeCall(
                ProtocolVersions.initialize,
                (
                    finalSystemOwner,
                    ProtocolVersion.wrap(requiredProtocolVersion),
                    ProtocolVersion.wrap(recommendedProtocolVersion)
                )
            )
        });

        ProtocolVersions _versions = ProtocolVersions(protocolVersionsProxy);
        string memory version = _versions.version();
        console.log("ProtocolVersions version: %s", version);

        ChainAssertions.checkProtocolVersions({ _contracts: _proxiesUnstrict(), _cfg: deployer.getConfig(), _isProxy: true });
    }

    function _proxiesUnstrict()
        internal
        view
        returns (Types.ContractSet memory proxies_)
    {
        proxies_ = Types.ContractSet({
            L1CrossDomainMessenger: deployer.getAddress("L1CrossDomainMessengerProxy"),
            L1StandardBridge: deployer.getAddress("L1StandardBridgeProxy"),
            L2OutputOracle: deployer.getAddress("L2OutputOracleProxy"),
            DisputeGameFactory: deployer.getAddress("DisputeGameFactoryProxy"),
            DelayedWETH: deployer.getAddress("DelayedWETHProxy"),
            AnchorStateRegistry: deployer.getAddress("AnchorStateRegistryProxy"),
            OptimismMintableERC20Factory: deployer.getAddress("OptimismMintableERC20FactoryProxy"),
            OptimismPortal: deployer.getAddress("OptimismPortalProxy"),
            OptimismPortal2: deployer.getAddress("OptimismPortalProxy"),
            SystemConfig: deployer.getAddress("SystemConfigProxy"),
            L1ERC721Bridge: deployer.getAddress("L1ERC721BridgeProxy"),
            ProtocolVersions: deployer.getAddress("ProtocolVersionsProxy"),
            SuperchainConfig: deployer.getAddress("SuperchainConfigProxy")
        });
    }
}

        
      

After running the deploy script, the address deployed is saved at deployments/31337/.save.json. Otherwise, as specified in .env.<network>.local.

(Alternative) : Deploy All

In your terminal, copy below contracts' codes and run deployment scripts to your prefered network:

forge script script/000_DeployAllScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast

(Optional), you can specify your derivation path:

--mnemonic-derivation-paths "m/44'/60'/0'/0/0"

Contract Settings

Step 1

Owner

Step 2

AddressManager ProxyAdmin SuperchainConfigProxy SuperchainConfig ProtocolVersionsProxy ProtocolVersions
Deploy Script:
000_DeployAllScript.s.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Script} from "@redprint-forge-std/Script.sol";
import {IDeployer, getDeployer} from "@redprint-deploy/deployer/DeployScript.sol";
import {DeploySafeProxyScript} from "@script/101_DeploySafeProxyScript.s.sol";
import {DeployAddressManagerScript} from "@script/201A_DeployAddressManagerScript.s.sol";
import {DeployAndSetupProxyAdminScript} from "@script/201B_DeployAndSetupProxyAdminScript.s.sol";
import {DeploySuperchainConfigProxyScript} from "@script/202A_DeploySuperchainConfigProxyScript.s.sol";
import {DeployAndInitializeSuperchainConfigScript} from "@script/202B_DeployAndInitializeSuperchainConfigScript.s.sol";
import {DeployProtocolVersionsProxyScript} from "@script/203A_DeployProtocolVersionsProxyScript.s.sol";
import {DeployAndInitializeProtocolVersionsScript} from "@script/203B_DeployAndInitializeProtocolVersionsScript.s.sol";

contract DeployAllScript is Script {
    IDeployer deployerProcedue;

    function run() public {
        deployerProcedue = getDeployer();
        deployerProcedue.setAutoSave(true);
        DeploySafeProxyScript safeDeployments = new DeploySafeProxyScript();
        //1) set up Safe Multisig
        safeDeployments.deploy();
        DeployAddressManagerScript addressManagerDeployments = new DeployAddressManagerScript();
        DeployAndSetupProxyAdminScript proxyAdminDeployments = new DeployAndSetupProxyAdminScript();

        DeploySuperchainConfigProxyScript superchainConfigProxyDeployments = new DeploySuperchainConfigProxyScript();
        DeployAndInitializeSuperchainConfigScript superchainConfigDeployments = new DeployAndInitializeSuperchainConfigScript();

        DeployProtocolVersionsProxyScript protocolVersionsProxyDeployments = new DeployProtocolVersionsProxyScript();
        DeployAndInitializeProtocolVersionsScript protocolVersionsDeployments = new DeployAndInitializeProtocolVersionsScript();

        // Deploy a new ProxyAdmin and AddressManager
        addressManagerDeployments.deploy();
        proxyAdminDeployments.deploy();
        proxyAdminDeployments.initialize();

        // Deploy the SuperchainConfigProxy
        superchainConfigProxyDeployments.deploy();
        superchainConfigDeployments.deploy();
        superchainConfigDeployments.initialize();

        // Deploy the ProtocolVersionsProxy
        protocolVersionsProxyDeployments.deploy();
        protocolVersionsDeployments.deploy();
        protocolVersionsDeployments.initialize();
    }
}

        
      

After running the deploy script, the address deployed is saved at deployments/31337/.save.json. Otherwise, as specified in .env.<network>.local.