Skip to content

Commit 8ed7d1f

Browse files
authored
feat: set up Base Bridge Base Sepolia deployment task (#435)
* set up Base Bridge Base Sepolia deployment task * update bridge commit * add requested comment * deploy devnet prod bridge * deploy devnet prod bridge
1 parent 0c86c2d commit 8ed7d1f

File tree

8 files changed

+1210
-0
lines changed

8 files changed

+1210
-0
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
OP_COMMIT=594bc933a38425f745b46399a3619bcdeb74965d
2+
BASE_CONTRACTS_COMMIT=98ec680a67c173d38aa52588c5dc0fbaa1c0561c
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
include ../../Makefile
2+
include ../.env
3+
include .env
4+
5+
.PHONY: deps
6+
deps:
7+
forge install --no-git github.com/base/bridge@24b6bcaec94eac34524bcd0774dd6ec14dc3bf21
8+
9+
.PHONY: deploy
10+
deploy:
11+
forge script DeployBridge --rpc-url $(L2_RPC_URL) \
12+
--sender $(shell cast wallet address --account testnet-admin) \
13+
--account testnet-admin --broadcast -vvvv
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Base Bridge Deployment
2+
3+
Status: EXECUTED
4+
5+
Deploys the Base side of [Base Bridge](https://github.com/base/bridge). This should be done after deploying the Solana bridge program since the program's pubkey needs to be added to `config.json`.
6+
7+
## Deployment Steps
8+
9+
1. Install dependencies
10+
11+
```bash
12+
cd sepolia/2025-10-17-base-bridge-deployment
13+
make deps
14+
```
15+
16+
2. Connect and unlock Ledger
17+
18+
3. Deploy bridge
19+
20+
```bash
21+
make deploy
22+
```
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"Bridge": "0x01824a90d32A69022DdAEcC6C5C14Ed08dB4EB9B",
3+
"BridgeValidator": "0x863Bed3E344035253CC44C75612Ad5fDF5904aEE",
4+
"CrossChainERC20Factory": "0x488EB7F7cb2568e31595D48cb26F63963Cc7565D",
5+
"Twin": "0x11bF22cFf007C46C725Dc59A919383326E3cdefB",
6+
"RelayerOrchestrator": "0x1e0842b2E6FA06A59b05a9c1d36a6480730012CE",
7+
"WrappedSol": "0xCace0c896714DaF7098FFD8CC54aFCFe0338b4BC"
8+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"salt": "0x90c21999d061600a9c6553e1e6feddb6c90ba96a617a140fe81e8195be404b6e",
3+
"initialOwner": "0x5dfEB066334B67355A15dc9b67317fD2a2e1f77f",
4+
"partnerValidators": "0x0000000000000000000000000000000000000001",
5+
"baseValidators": [
6+
"0x2880a6DcC8c87dD2874bCBB9ad7E627a407Cf3C2",
7+
"0xc5fe09f194C01e56fB89cC1155daE033D20cDCc7"
8+
],
9+
"baseSignatureThreshold": 2,
10+
"partnerValidatorThreshold": 0,
11+
"remoteBridge": "0x6223fa80b3f80549a9ac4f1deea54e39a74aa8fa8f74a62131460716519d4f8f",
12+
"guardians": ["0x5dfEB066334B67355A15dc9b67317fD2a2e1f77f"]
13+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[profile.default]
2+
src = 'src'
3+
out = 'out'
4+
libs = ['lib']
5+
broadcast = 'records'
6+
fs_permissions = [{ access = "read-write", path = "./" }]
7+
optimizer = true
8+
optimizer_runs = 999999
9+
solc_version = "0.8.28"
10+
via-ir = false
11+
remappings = [
12+
'@eth-optimism-bedrock/=lib/optimism/packages/contracts-bedrock/',
13+
'@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts',
14+
'@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts',
15+
'@rari-capital/solmate/=lib/solmate/',
16+
'@base-contracts/=lib/base-contracts',
17+
'@solady/=lib/solady/src/',
18+
]
19+
20+
# See more config options https://github.com/foundry-rs/foundry/tree/master/config

sepolia/2025-10-17-base-bridge-deployment/records/DeployBridge.s.sol/84532/run-1761256132305.json

Lines changed: 882 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.28;
3+
4+
import {Script} from "forge-std/Script.sol";
5+
import {stdJson} from "forge-std/StdJson.sol";
6+
import {ERC1967Factory} from "@solady/utils/ERC1967Factory.sol";
7+
import {ERC1967FactoryConstants} from "@solady/utils/ERC1967FactoryConstants.sol";
8+
import {LibString} from "solady/utils/LibString.sol";
9+
import {UpgradeableBeacon} from "@solady/utils/UpgradeableBeacon.sol";
10+
import {AddressAliasHelper} from "@eth-optimism-bedrock/src/vendor/AddressAliasHelper.sol";
11+
12+
import {Pubkey} from "bridge/libraries/SVMLib.sol";
13+
import {TokenLib} from "bridge/libraries/TokenLib.sol";
14+
import {RelayerOrchestrator} from "bridge/periphery/RelayerOrchestrator.sol";
15+
import {Bridge} from "bridge/Bridge.sol";
16+
import {BridgeValidator} from "bridge/BridgeValidator.sol";
17+
import {CrossChainERC20} from "bridge/CrossChainERC20.sol";
18+
import {CrossChainERC20Factory} from "bridge/CrossChainERC20Factory.sol";
19+
import {Twin} from "bridge/Twin.sol";
20+
21+
struct Cfg {
22+
bytes32 salt;
23+
address erc1967Factory;
24+
address initialOwner;
25+
address partnerValidators;
26+
address[] baseValidators;
27+
uint128 baseSignatureThreshold;
28+
uint256 partnerValidatorThreshold;
29+
Pubkey remoteBridge;
30+
address[] guardians;
31+
}
32+
33+
contract DeployBridge is Script {
34+
using stdJson for string;
35+
using AddressAliasHelper for address;
36+
37+
string public cfgData;
38+
Cfg public cfg;
39+
40+
function setUp() public {
41+
cfgData = vm.readFile(string.concat(vm.projectRoot(), "/config.json"));
42+
43+
cfg.salt = _readBytes32FromConfig("salt");
44+
cfg.erc1967Factory = ERC1967FactoryConstants.ADDRESS;
45+
cfg.initialOwner = _readAddressFromConfig("initialOwner").applyL1ToL2Alias();
46+
cfg.partnerValidators = _readAddressFromConfig("partnerValidators");
47+
cfg.baseValidators = _readAddressArrayFromConfig("baseValidators");
48+
cfg.baseSignatureThreshold = uint128(_readUintFromConfig("baseSignatureThreshold"));
49+
cfg.partnerValidatorThreshold = _readUintFromConfig("partnerValidatorThreshold");
50+
cfg.remoteBridge = Pubkey.wrap(_readBytes32FromConfig("remoteBridge"));
51+
cfg.guardians = _readAddressArrayFromConfig("guardians");
52+
53+
require(cfg.guardians.length == 1, "invalid guardians length");
54+
cfg.guardians[0] = cfg.guardians[0].applyL1ToL2Alias();
55+
}
56+
57+
function run() public {
58+
address precomputedBridgeAddress = ERC1967Factory(cfg.erc1967Factory).predictDeterministicAddress(_salt());
59+
60+
vm.startBroadcast();
61+
address twinBeacon = _deployTwinBeacon({precomputedBridgeAddress: precomputedBridgeAddress});
62+
address factory = _deployFactory({precomputedBridgeAddress: precomputedBridgeAddress});
63+
address bridgeValidator = _deployBridgeValidator({bridge: precomputedBridgeAddress});
64+
address bridge =
65+
_deployBridge({twinBeacon: twinBeacon, crossChainErc20Factory: factory, bridgeValidator: bridgeValidator});
66+
address relayerOrchestrator = _deployRelayerOrchestrator({bridge: bridge, bridgeValidator: bridgeValidator});
67+
address sol = CrossChainERC20Factory(factory).deploySolWrapper();
68+
vm.stopBroadcast();
69+
70+
require(address(bridge) == precomputedBridgeAddress, "Bridge address mismatch");
71+
72+
_serializeAddress({key: "Bridge", value: bridge});
73+
_serializeAddress({key: "BridgeValidator", value: bridgeValidator});
74+
_serializeAddress({key: "CrossChainERC20Factory", value: factory});
75+
_serializeAddress({key: "Twin", value: twinBeacon});
76+
_serializeAddress({key: "RelayerOrchestrator", value: relayerOrchestrator});
77+
_serializeAddress({key: "WrappedSol", value: sol});
78+
79+
_postCheck(twinBeacon, factory, bridgeValidator, bridge, relayerOrchestrator, sol);
80+
}
81+
82+
function _postCheck(
83+
address twinBeacon,
84+
address factory,
85+
address bridgeValidator,
86+
address bridge,
87+
address relayerOrchestrator,
88+
address sol
89+
) private view {
90+
// Twin
91+
Twin twinImpl = Twin(payable(UpgradeableBeacon(twinBeacon).implementation()));
92+
require(twinImpl.BRIDGE() == bridge, "PC01: incorrect bridge address in twin impl");
93+
94+
// Factory
95+
UpgradeableBeacon tokenBeacon = UpgradeableBeacon(CrossChainERC20Factory(factory).BEACON());
96+
CrossChainERC20 tokenImpl = CrossChainERC20(tokenBeacon.implementation());
97+
require(tokenImpl.bridge() == bridge, "PC02: incorrect bridge address in token impl");
98+
99+
// BridgeValidator
100+
require(
101+
BridgeValidator(bridgeValidator).BRIDGE() == bridge, "PC03: incorrect bridge address in BridgeValidator"
102+
);
103+
require(
104+
BridgeValidator(bridgeValidator).PARTNER_VALIDATORS() == cfg.partnerValidators,
105+
"PC04: incorrect partnerValidators address in BridgeValidator"
106+
);
107+
require(
108+
BridgeValidator(bridgeValidator).partnerValidatorThreshold() == cfg.partnerValidatorThreshold,
109+
"PC05: incorrect partner validator threshold in BridgeValidator"
110+
);
111+
require(
112+
BridgeValidator(bridgeValidator).getBaseThreshold() == cfg.baseSignatureThreshold,
113+
"PC06: incorrect Base threshold in BridgeValidator"
114+
);
115+
require(
116+
BridgeValidator(bridgeValidator).getBaseValidatorCount() == cfg.baseValidators.length,
117+
"PC07: incorrect registered base validator count"
118+
);
119+
120+
for (uint256 i; i < cfg.baseValidators.length; i++) {
121+
require(
122+
BridgeValidator(bridgeValidator).isBaseValidator(cfg.baseValidators[i]),
123+
"PC08: base validator not registered"
124+
);
125+
}
126+
127+
// Bridge
128+
require(Bridge(bridge).REMOTE_BRIDGE() == cfg.remoteBridge, "PC09: incorrect remote bridge in Bridge contract");
129+
require(Bridge(bridge).TWIN_BEACON() == twinBeacon, "PC10: incorrect twin beacon in Bridge contract");
130+
require(Bridge(bridge).CROSS_CHAIN_ERC20_FACTORY() == factory, "PC11: incorrect factory in Bridge contract");
131+
require(
132+
Bridge(bridge).BRIDGE_VALIDATOR() == bridgeValidator, "PC12: incorrect bridge validator in Bridge contract"
133+
);
134+
require(Bridge(bridge).owner() == cfg.initialOwner, "PC13: incorrect Bridge owner");
135+
136+
for (uint256 i; i < cfg.guardians.length; i++) {
137+
require(
138+
Bridge(bridge).rolesOf(cfg.guardians[i]) == Bridge(bridge).GUARDIAN_ROLE(),
139+
"PC14: guardian missing perms"
140+
);
141+
}
142+
143+
// RelayerOrchestrator
144+
require(
145+
RelayerOrchestrator(relayerOrchestrator).BRIDGE() == bridge, "PC15: incorrect bridge in RelayerOrchestrator"
146+
);
147+
require(
148+
RelayerOrchestrator(relayerOrchestrator).BRIDGE_VALIDATOR() == bridgeValidator,
149+
"PC16: incorrect bridge validator in RelayerOrchestrator"
150+
);
151+
152+
// SOL
153+
require(CrossChainERC20(sol).bridge() == bridge, "PC17: incorrect bridge in SOL contract");
154+
require(LibString.eq(CrossChainERC20(sol).name(), "Solana"), "PC18: incorrect SOL name");
155+
require(LibString.eq(CrossChainERC20(sol).symbol(), "SOL"), "PC19: incorrect SOL symbol");
156+
require(
157+
CrossChainERC20(sol).remoteToken() == Pubkey.unwrap(TokenLib.NATIVE_SOL_PUBKEY),
158+
"PC20: incorrect SOL remote token"
159+
);
160+
require(CrossChainERC20(sol).decimals() == 9, "PC21: incorrect SOL decimals");
161+
}
162+
163+
function _deployTwinBeacon(address precomputedBridgeAddress) private returns (address) {
164+
address twinImpl = address(new Twin(precomputedBridgeAddress));
165+
return address(new UpgradeableBeacon({initialOwner: cfg.initialOwner, initialImplementation: twinImpl}));
166+
}
167+
168+
function _deployFactory(address precomputedBridgeAddress) private returns (address) {
169+
address erc20Impl = address(new CrossChainERC20(precomputedBridgeAddress));
170+
address erc20Beacon =
171+
address(new UpgradeableBeacon({initialOwner: cfg.initialOwner, initialImplementation: erc20Impl}));
172+
173+
address xChainErc20FactoryImpl = address(new CrossChainERC20Factory(erc20Beacon));
174+
return
175+
ERC1967Factory(cfg.erc1967Factory).deploy({implementation: xChainErc20FactoryImpl, admin: cfg.initialOwner});
176+
}
177+
178+
function _deployBridgeValidator(address bridge) private returns (address) {
179+
address bridgeValidatorImpl =
180+
address(new BridgeValidator({bridgeAddress: bridge, partnerValidators: cfg.partnerValidators}));
181+
182+
return ERC1967Factory(cfg.erc1967Factory)
183+
.deployAndCall({
184+
implementation: bridgeValidatorImpl,
185+
admin: cfg.initialOwner,
186+
data: abi.encodeCall(
187+
BridgeValidator.initialize,
188+
(cfg.baseValidators, cfg.baseSignatureThreshold, cfg.partnerValidatorThreshold)
189+
)
190+
});
191+
}
192+
193+
function _deployBridge(address twinBeacon, address crossChainErc20Factory, address bridgeValidator)
194+
private
195+
returns (address)
196+
{
197+
Bridge bridgeImpl = new Bridge({
198+
remoteBridge: cfg.remoteBridge,
199+
twinBeacon: twinBeacon,
200+
crossChainErc20Factory: crossChainErc20Factory,
201+
bridgeValidator: bridgeValidator
202+
});
203+
204+
return ERC1967Factory(cfg.erc1967Factory)
205+
.deployDeterministicAndCall({
206+
implementation: address(bridgeImpl),
207+
admin: cfg.initialOwner,
208+
salt: _salt(),
209+
data: abi.encodeCall(Bridge.initialize, (cfg.initialOwner, cfg.guardians))
210+
});
211+
}
212+
213+
function _deployRelayerOrchestrator(address bridge, address bridgeValidator) private returns (address) {
214+
address relayerOrchestratorImpl =
215+
address(new RelayerOrchestrator({bridge: bridge, bridgeValidator: bridgeValidator}));
216+
217+
return
218+
ERC1967Factory(cfg.erc1967Factory)
219+
.deploy({implementation: relayerOrchestratorImpl, admin: cfg.initialOwner});
220+
}
221+
222+
function _serializeAddress(string memory key, address value) private {
223+
vm.writeJson({
224+
json: LibString.toHexStringChecksummed(value), path: "addresses.json", valueKey: string.concat(".", key)
225+
});
226+
}
227+
228+
function _readAddressFromConfig(string memory key) private view returns (address) {
229+
return vm.parseJsonAddress({json: cfgData, key: string.concat(".", key)});
230+
}
231+
232+
function _readAddressArrayFromConfig(string memory key) private view returns (address[] memory) {
233+
return vm.parseJsonAddressArray({json: cfgData, key: string.concat(".", key)});
234+
}
235+
236+
function _readUintFromConfig(string memory key) private view returns (uint256) {
237+
return vm.parseJsonUint({json: cfgData, key: string.concat(".", key)});
238+
}
239+
240+
function _readBytes32FromConfig(string memory key) private view returns (bytes32) {
241+
return vm.parseJsonBytes32({json: cfgData, key: string.concat(".", key)});
242+
}
243+
244+
/// @dev Appends `msg.sender` to the front of the salt to satisfy a requirement
245+
/// of the `ERC1967Factory.deployDeterministicAndCall()` method.
246+
function _salt() private view returns (bytes32) {
247+
bytes12 s = bytes12(keccak256(abi.encode(cfg.salt)));
248+
return bytes32(abi.encodePacked(msg.sender, s));
249+
}
250+
}

0 commit comments

Comments
 (0)