Skip to content
Open
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
9 changes: 9 additions & 0 deletions mainnet/2025-12-05-deploy-cb-op-multisig-smartescrow/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
OP_COMMIT=d09c836f818c73ae139f60b717654c4e53712743
BASE_CONTRACTS_COMMIT=9526d38b63be2ee10fc905dee60f0b2a0a17e89e

SAFE_PROXY_FACTORY=0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67
L1_GNOSIS_SAFE_IMPLEMENTATION=0x41675C099F32341bf84BFc5382aF534df5C7461a
L1_GNOSIS_COMPATIBILITY_FALLBACK_HANDLER=0xfd0732Dc9E303f09fCEf3a7388Ad10A83459Ec99

CB_NESTED_SAFE=0x9C4a57Feb77e294Fd7BF5EBE9AB01CAA0a90A110
OP_SIGNER_SAFE=0x9BA6e03D8B90dE867373Db8cF1A58d2F7F006b3A
20 changes: 20 additions & 0 deletions mainnet/2025-12-05-deploy-cb-op-multisig-smartescrow/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
include ../../Makefile
include ../../Multisig.mk
include ../.env
include .env

ifndef LEDGER_ACCOUNT
override LEDGER_ACCOUNT = 0
endif

SCRIPT_NAME = DeploySafe

RPC_URL = $(L1_RPC_URL)

.PHONY: safe-deps
safe-deps:
forge install --no-git safe-global/safe-smart-account@21dc82410445637820f600c7399a804ad55841d5

.PHONY: deploy
deploy:
forge script --rpc-url $(RPC_URL) $(SCRIPT_NAME) --ledger --hd-paths "m/44'/60'/$(LEDGER_ACCOUNT)'/0/0"
34 changes: 34 additions & 0 deletions mainnet/2025-12-05-deploy-cb-op-multisig-smartescrow/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Deploy New CB and OP MultiSig for SmartEscrow

Status: READY TO SIGN

## Procedure

### 1. Update repo:

```bash
cd contract-deployments
git pull
cd mainnet/2025-12-05-deploy-cb-op-multisig-smartescrow
make deps
```

### 2. Setup Ledger

Your Ledger needs to be connected and unlocked. The Ethereum
application needs to be opened on Ledger with the message "Application
is ready".

### 3. Run relevant script(s)

#### 3.1 Deploy new Safes

```bash
make safe-deps
```

```bash
make deploy
```

This will output the new addresses of the new `Safe` contract to an `addresses.json` file. You will need to commit this file to the repo.
19 changes: 19 additions & 0 deletions mainnet/2025-12-05-deploy-cb-op-multisig-smartescrow/foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[profile.default]
src = 'src'
out = 'out'
libs = ['lib']
broadcast = 'records'
fs_permissions = [{ access = "read-write", path = "./" }]
optimizer = true
optimizer_runs = 999999
via-ir = false
remappings = [
'@eth-optimism-bedrock/=lib/optimism/packages/contracts-bedrock/',
'@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts',
'@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts',
'@rari-capital/solmate/=lib/solmate/',
'@base-contracts/=lib/base-contracts',
'solady/=lib/solady/src/',
]

# See more config options https://github.com/foundry-rs/foundry/tree/master/config
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {stdJson} from "forge-std/StdJson.sol";
import {Script} from "forge-std/Script.sol";
import {Safe} from "safe-smart-account/contracts/Safe.sol";
import {SafeProxyFactory} from "safe-smart-account/contracts/proxies/SafeProxyFactory.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {console} from "forge-std/console.sol";

contract DeploySafe is Script {
using Strings for address;
using stdJson for string;

address public constant Z_ADDR = address(0);

address public immutable SAFE_IMPLEMENTATION;
address public immutable FALLBACK_HANDLER;
address public immutable SAFE_PROXY_FACTORY;
address public immutable CB_NESTED_SAFE;
address public immutable OP_SIGNER_SAFE;

constructor() {
SAFE_IMPLEMENTATION = vm.envAddress("L1_GNOSIS_SAFE_IMPLEMENTATION");
FALLBACK_HANDLER = vm.envAddress("L1_GNOSIS_COMPATIBILITY_FALLBACK_HANDLER");
SAFE_PROXY_FACTORY = vm.envAddress("SAFE_PROXY_FACTORY");

CB_NESTED_SAFE = vm.envAddress("CB_NESTED_SAFE");
OP_SIGNER_SAFE = vm.envAddress("OP_SIGNER_SAFE");
}

function run() public {
address[] memory owners = new address[](2);
owners[0] = CB_NESTED_SAFE;
owners[1] = OP_SIGNER_SAFE;

console.log("Deploying Safe with owners:");
_printOwners(owners);

vm.startBroadcast();
// First safe maintains the same owners + threshold as the current owner safe
address safe = _createAndInitProxy(owners, 2);
vm.stopBroadcast();
_postCheck(safe);

vm.writeFile("addresses.json", string.concat("{", "\"Safe\": \"", safe.toHexString(), "}"));
}

function _postCheck(address safeAddress) private view {
Safe safe = Safe(payable(safeAddress));

address[] memory safeOwners = safe.getOwners();
uint256 safeThreshold = safe.getThreshold();

require(safeThreshold == 2, "PostCheck 1");
require(safeOwners.length == 2, "PostCheck 2");

require(safeOwners[0] == CB_NESTED_SAFE);
require(safeOwners[1] == OP_SIGNER_SAFE);

console.log("PostCheck passed");
}

function _createAndInitProxy(address[] memory owners, uint256 threshold) private returns (address) {
bytes memory initializer =
abi.encodeCall(Safe.setup, (owners, threshold, Z_ADDR, "", FALLBACK_HANDLER, Z_ADDR, 0, payable(Z_ADDR)));
return address(SafeProxyFactory(SAFE_PROXY_FACTORY).createProxyWithNonce(SAFE_IMPLEMENTATION, initializer, 0));
}

function _printOwners(address[] memory owners) private pure {
for (uint256 i; i < owners.length; i++) {
console.logAddress(owners[i]);
}
}
}