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
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
pnpm add redprint-forge
[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/)
( e.g. hardhat.json, mainnet.json or optimism.json)
{
"l1StartingBlockTag": "0x28665aedb1e78a65e0878347df30f116e5652ce24ede025a80d603656536074f",
"l1ChainID": 11155111,
"l2ChainID": 42069,
"l2BlockTime": 2,
"l1BlockTime": 12,
"maxSequencerDrift": 600,
"sequencerWindowSize": 3600,
"channelTimeout": 300,
"p2pSequencerAddress": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"batchInboxAddress": "0xff00000000000000000000000000000000042069",
"batchSenderAddress": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"l2OutputOracleSubmissionInterval": 120,
"l2OutputOracleStartingBlockNumber": 0,
"l2OutputOracleStartingTimestamp": 1709796684,
"l2OutputOracleProposer": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"l2OutputOracleChallenger": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"finalizationPeriodSeconds": 12,
"proxyAdminOwner": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"baseFeeVaultRecipient": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"l1FeeVaultRecipient": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"sequencerFeeVaultRecipient": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"finalSystemOwner": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"superchainConfigGuardian": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"baseFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000",
"l1FeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000",
"sequencerFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000",
"baseFeeVaultWithdrawalNetwork": 0,
"l1FeeVaultWithdrawalNetwork": 0,
"sequencerFeeVaultWithdrawalNetwork": 0,
"gasPriceOracleOverhead": 2100,
"gasPriceOracleScalar": 1000000,
"enableGovernance": true,
"governanceTokenSymbol": "OP",
"governanceTokenName": "Optimism",
"governanceTokenOwner": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"l2GenesisBlockGasLimit": "0x1c9c380",
"l2GenesisBlockBaseFeePerGas": "0x3b9aca00",
"l2GenesisRegolithTimeOffset": "0x0",
"eip1559Denominator": 50,
"eip1559DenominatorCanyon": 250,
"eip1559Elasticity": 6,
"l2GenesisDeltaTimeOffset": null,
"l2GenesisCanyonTimeOffset": "0x0",
"systemConfigStartBlock": 0,
"requiredProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000",
"recommendedProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000",
"faultGameAbsolutePrestate": "0x0000000000000000000000000000000000000000000000000000000000000000",
"faultGameMaxDepth": 8,
"faultGameClockExtension": 0,
"faultGameMaxClockDuration": 1200,
"faultGameGenesisBlock": 0,
"faultGameGenesisOutputRoot": "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF",
"faultGameSplitDepth": 4,
"faultGameWithdrawalDelay": 604800,
"preimageOracleMinProposalSize": 1800000,
"preimageOracleChallengePeriod": 120,
"preimageOracleCancunActivationTimestamp": 0
}
click on ✕ button to close
This following file uses foundry's default test private keys.
DONT forget to change MNEMONIC, DEPLOYER_PRIVATE_KEY and DEPLOYER_ADDRESS to be yours!
RPC_URL_localhost=http://localhost:8545
#secret management
MNEMONIC="test test test test test test test test test test test junk"
# local network 's default private key so it is still not exposed
DEPLOYER_PRIVATE_KEY=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
DEPLOYER_ADDRESS=0x70997970C51812dc3A010C7d01b50e0d17dc79C8
# script/Config.sol
DEPLOYMENT_OUTFILE=deployments/31337/.save.json
DEPLOY_CONFIG_PATH=deploy-config/hardhat.json
CHAIN_ID=
IMPL_SALT=$(openssl rand -hex 32)
STATE_DUMP_PATH=
SIG=
DEPLOY_FILE=
DRIPPIE_OWNER_PRIVATE_KEY=9000
# deploy-Config
GS_ADMIN_ADDRESS=0x70997970C51812dc3A010C7d01b50e0d17dc79C8
GS_BATCHER_ADDRESS=0x70997970C51812dc3A010C7d01b50e0d17dc79C8
GS_PROPOSER_ADDRESS=0x70997970C51812dc3A010C7d01b50e0d17dc79C8
GS_SEQUENCER_ADDRESS=0x70997970C51812dc3A010C7d01b50e0d17dc79C8
L1_RPC_URL=http://localhost:8545
click on ✕ button to close
Find out more about our guide on github
Note: This script is not completed yet. It is for one layer only. Check our final script for full deployment.
forge script script/000_DeployAllScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast
--mnemonic-derivation-paths "m/44'/60'/0'/0/0"
// 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.
Your saved address will be different.
You can change DEPLOYMENT_OUTFILE=deployments/31337/.save.json to reflect yours!
{
"SafeProxyFactory": "0x41C3c259514f88211c4CA2fd805A93F8F9A57504",
"SafeSingleton": "0x0401911641c4781D93c41f9aa8094B171368E6a9",
"SystemOwnerSafe": "0x31Ce59Df6F742e1C83f00427F09DCAaF0765DF3b"
}
click on ✕ button to close
1.1 : Deploy Governance Contract
forge script script/101_DeploySafeProxyScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast
--mnemonic-derivation-paths "m/44'/60'/0'/0/0"
// 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())
}
}
}
// 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.
Your saved address will be different.
You can change DEPLOYMENT_OUTFILE=deployments/31337/.save.json to reflect yours!
{
"SafeProxyFactory": "<ADDRESS_1>",
"SafeSingleton": "<ADDRESS_2>",
"SystemOwnerSafe": "<ADDRESS_3>"
}
click on ✕ button to close