Quick Guide !!

We have prepared a set of scripts to deploy all contracts in one click. Otherwise, you can look into and customize each contract as you like.

we reference to OPStack 's version of v1.9.4

1.0 : Prerequisites

Fork optimism 's monorepo:

git clone --depth 1 --branch v1.9.4 https://github.com/ethereum-optimism/optimism.git

All OPStack's contracts are based on v1.9.4

Add the redprint-forge using your favorite package manager, e.g., with pnpm:

pnpm add redprint-forge

Modify OPStack's foundry.toml to the root directory with following:

[profile.default]

# Compilation settings
src = 'src'
out = 'forge-artifacts'
script = 'scripts'
optimizer = true
optimizer_runs = 999999
remappings = [
'@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts',
'@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts',
'@openzeppelin/contracts-v5/=lib/openzeppelin-contracts-v5/contracts',
'@rari-capital/solmate/=lib/solmate',
'@lib-keccak/=lib/lib-keccak/contracts/lib',
'@solady/=lib/solady/src',
'forge-std/=lib/forge-std/src',
'ds-test/=lib/forge-std/lib/ds-test/src',
'safe-contracts/=lib/safe-contracts/contracts',
'kontrol-cheatcodes/=lib/kontrol-cheatcodes/src',
'gelato/=lib/automate/contracts'
'@redprint-core/=src/',
'@redprint-deploy/=node_modules/redprint-forge/script',
'@scripts/=scripts/',
'@redprint-test/=node_modules/redprint-forge/test/',
'@redprint-forge-std/=lib/forge-std/src',
'@redprint-openzeppelin/=lib/openzeppelin-contracts/contracts',
'@redprint-openzeppelin-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts',
'@redprint-safe-contracts/=lib/safe-contracts/contracts',
'@redprint-lib-keccak/=lib/lib-keccak/contracts/lib',
'@redprint-solady/=lib/solady/src',
]
...

We use @redprint-/ as a convention to avoid any naming conflicts with your previously installed libararies ( i.e. @redprint-forge-std/ vs @forge-std/)

Add the deploy config file< network >.json to your desired directory ( i.e script/deploy-config/hardhat.json ) with following:

Add .envand modify as required.

Find out more about our guide on github

One-Click Governance Layer Deployment

Note: This script is not completed yet. It is for one layer only. Check our final script for full deployment.

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
Deploy Script:
000_DeployAllScript.s.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {DeploySafeProxyScript} from "@scripts/101_DeploySafeProxyScript.s.sol";
import {IDeployer, getDeployer} from "@redprint-deploy/deployer/DeployScript.sol";
import {Script} from "@redprint-forge-std/Script.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();
    }
}

        
      

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

1.1 : Deploy Governance Contract

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

forge script script/101_DeploySafeProxyScript.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"

The default contract is MultiSig.

Contract Settings

Deploy Script Settings

Chains

OpSec Management

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

/**
 * @title IProxy - Helper interface to access the singleton address of the Proxy on-chain.
 * @author Richard Meissner - @rmeissner
 */
interface IProxy {
    function masterCopy() external view returns (address);
}

/// @custom:security-contact Consult full code at https://github.com/safe-global/safe-smart-account/blob/a9e3385bb38c29d45b3901ff7180b59fcee86ac9/contracts/proxies/SafeProxy.sol
contract GnosisSafeProxy {
    address internal singleton;

    constructor(address _singleton) {
        require(_singleton != address(0), "Invalid singleton address provided");
        singleton = _singleton;
    }

    fallback() external payable {
        // solhint-disable-next-line no-inline-assembly
        assembly {
          let _singleton := sload(0)
          // 0xa619486e == keccak("masterCopy()"). The value is right padded to 32-bytes with 0s
          if eq(calldataload(0), 0xa619486e00000000000000000000000000000000000000000000000000000000) {
              mstore(0, shr(12, shl(12, _singleton)))
              return(0, 0x20)
          }
          calldatacopy(0, 0, calldatasize())
          let success := delegatecall(gas(), _singleton, 0, calldatasize(), 0, 0)
          returndatacopy(0, 0, returndatasize())
          if eq(success, 0) {
              revert(0, returndatasize())
          }
          return(0, returndatasize())
        }
    }
}

        
      
Deploy Script:
101_DeploySafeProxyScript.s.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {DeployScript} from "@redprint-deploy/deployer/DeployScript.sol";
import {DeployerFunctions, IDeployer} from "@redprint-deploy/deployer/DeployerFunctions.sol";
import {GnosisSafe as Safe} from "@redprint-safe-contracts/GnosisSafe.sol";
import {GnosisSafeProxy as SafeProxy} from "@redprint-safe-contracts/proxies/GnosisSafeProxy.sol";
import {GnosisSafeProxyFactory as SafeProxyFactory} from "@redprint-safe-contracts/proxies/GnosisSafeProxyFactory.sol";
import {console} from "@redprint-forge-std/console.sol";

/// @custom:security-contact Consult full internal deploy script at https://github.com/Ratimon/redprint-forge
contract DeploySafeProxyScript is DeployScript {
    using DeployerFunctions for IDeployer ;
    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 (SafeProxyFactory safeProxyFactory_, Safe safeSingleton_, SafeProxy safeProxy_)
    {
        console.log("Setup Governance ... ");

        address safeProxyFactory = 0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2;
        address safeSingleton = 0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552;
        safeProxyFactory.code.length == 0
            ? safeProxyFactory_ = SafeProxyFactory(deployer.deploy_SafeProxyFactory("SafeProxyFactory"))
            : safeProxyFactory_ = SafeProxyFactory(safeProxyFactory);

        safeSingleton.code.length == 0
            ? safeSingleton_ = Safe(deployer.deploy_Safe("SafeSingleton"))
            : safeSingleton_ = Safe(payable(safeSingleton));

        safeProxy_ = SafeProxy(
            deployer.deploy_SystemOwnerSafe("SystemOwnerSafe", "SafeProxyFactory", "SafeSingleton", address(owner), DeployScript.implSalt())
        );
    }
}

        
      

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