Skip to content
Merged
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
2 changes: 2 additions & 0 deletions sepolia/2025-10-17-base-bridge-alpha-deployment/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
OP_COMMIT=594bc933a38425f745b46399a3619bcdeb74965d
BASE_CONTRACTS_COMMIT=98ec680a67c173d38aa52588c5dc0fbaa1c0561c
13 changes: 13 additions & 0 deletions sepolia/2025-10-17-base-bridge-alpha-deployment/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
include ../../Makefile
include ../.env
include .env

.PHONY: deps
deps:
forge install --no-git github.com/base/bridge@e9b5fe27280ad2345d823d42668810432b0651aa

.PHONY: deploy
deploy:
forge script DeployBridge --rpc-url $(L2_RPC_URL) \
--sender $(shell cast wallet address --account testnet-admin) \
--account testnet-admin --broadcast -vvvv
18 changes: 18 additions & 0 deletions sepolia/2025-10-17-base-bridge-alpha-deployment/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Base Bridge Deployment

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`.

## Deployment Steps

1. Install dependencies

```bash
cd sepolia/2025-10-17-base-bridge-alpha-deployment
make deps
```

2. Deploy bridge

```bash
make deploy
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Bridge": "0x64567a9147fa89B1edc987e36Eb6f4b6db71656b",
"BridgeValidator": "0x41775f86710572Ec9AbeA4955A3dE882e0930675",
"CrossChainERC20Factory": "0xa51473BC986c95a5E1a7F9A9991e3f2f263842bE",
"Twin": "0x013d06E4cf47229D973Cb3C4e23Ff8D336CE5FF6",
"RelayerOrchestrator": "0x815170c71B25c6aA9891F386f96d2e84cC682149",
"WrappedSol": "0x003512146Fd54b71f926C7Fd4B7bd20Fc84E22c5"
}
13 changes: 13 additions & 0 deletions sepolia/2025-10-17-base-bridge-alpha-deployment/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"salt": "0xa9252d7f6f4f65eaaecf9228b26ebb5ed5c03771fd884c8c07d639ac2ce40e55",
"initialOwner": "0x5dfEB066334B67355A15dc9b67317fD2a2e1f77f",
"partnerValidators": "0x0000000000000000000000000000000000000001",
"baseValidators": [
"0x0e9a877906EBc3b7098DA2404412BF0Ed1A5EFb4",
"0x6D0E9C04BD896608b7e10b87FB686E1Feba85510"
],
"baseSignatureThreshold": 2,
"partnerValidatorThreshold": 0,
"remoteBridge": "0x5270f413294ec2409da350d920dc5d0e691b65fb868577de3a359b79bee6b4ac",
"guardians": ["0x5dfEB066334B67355A15dc9b67317fD2a2e1f77f"]
}
20 changes: 20 additions & 0 deletions sepolia/2025-10-17-base-bridge-alpha-deployment/foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[profile.default]
src = 'src'
out = 'out'
libs = ['lib']
broadcast = 'records'
fs_permissions = [{ access = "read-write", path = "./" }]
optimizer = true
optimizer_runs = 999999
solc_version = "0.8.28"
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

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import {Script} from "forge-std/Script.sol";
import {stdJson} from "forge-std/StdJson.sol";
import {ERC1967Factory} from "@solady/utils/ERC1967Factory.sol";
import {ERC1967FactoryConstants} from "@solady/utils/ERC1967FactoryConstants.sol";
import {LibString} from "solady/utils/LibString.sol";
import {UpgradeableBeacon} from "@solady/utils/UpgradeableBeacon.sol";
import {AddressAliasHelper} from "@eth-optimism-bedrock/src/vendor/AddressAliasHelper.sol";

import {Pubkey} from "bridge/libraries/SVMLib.sol";
import {TokenLib} from "bridge/libraries/TokenLib.sol";
import {RelayerOrchestrator} from "bridge/periphery/RelayerOrchestrator.sol";
import {Bridge} from "bridge/Bridge.sol";
import {BridgeValidator} from "bridge/BridgeValidator.sol";
import {CrossChainERC20} from "bridge/CrossChainERC20.sol";
import {CrossChainERC20Factory} from "bridge/CrossChainERC20Factory.sol";
import {Twin} from "bridge/Twin.sol";

struct Cfg {
bytes32 salt;
address erc1967Factory;
address initialOwner;
address partnerValidators;
address[] baseValidators;
uint128 baseSignatureThreshold;
uint256 partnerValidatorThreshold;
Pubkey remoteBridge;
address[] guardians;
}

contract DeployBridge is Script {
using stdJson for string;
using AddressAliasHelper for address;

string public cfgData;
Cfg public cfg;

function setUp() public {
cfgData = vm.readFile(string.concat(vm.projectRoot(), "/config.json"));

cfg.salt = _readBytes32FromConfig("salt");
cfg.erc1967Factory = ERC1967FactoryConstants.ADDRESS;
cfg.initialOwner = _readAddressFromConfig("initialOwner").applyL1ToL2Alias();
cfg.partnerValidators = _readAddressFromConfig("partnerValidators");
cfg.baseValidators = _readAddressArrayFromConfig("baseValidators");
cfg.baseSignatureThreshold = uint128(_readUintFromConfig("baseSignatureThreshold"));
cfg.partnerValidatorThreshold = _readUintFromConfig("partnerValidatorThreshold");
cfg.remoteBridge = Pubkey.wrap(_readBytes32FromConfig("remoteBridge"));
cfg.guardians = _readAddressArrayFromConfig("guardians");

require(cfg.guardians.length == 1, "invalid guardians length");
cfg.guardians[0] = cfg.guardians[0].applyL1ToL2Alias();
}

function run() public {
address precomputedBridgeAddress = ERC1967Factory(cfg.erc1967Factory).predictDeterministicAddress(_salt());

vm.startBroadcast();
address twinBeacon = _deployTwinBeacon({precomputedBridgeAddress: precomputedBridgeAddress});
address factory = _deployFactory({precomputedBridgeAddress: precomputedBridgeAddress});
address bridgeValidator = _deployBridgeValidator({bridge: precomputedBridgeAddress});
address bridge =
_deployBridge({twinBeacon: twinBeacon, crossChainErc20Factory: factory, bridgeValidator: bridgeValidator});
address relayerOrchestrator = _deployRelayerOrchestrator({bridge: bridge, bridgeValidator: bridgeValidator});
address sol = CrossChainERC20Factory(factory).deploySolWrapper();
vm.stopBroadcast();

require(address(bridge) == precomputedBridgeAddress, "Bridge address mismatch");

_serializeAddress({key: "Bridge", value: bridge});
_serializeAddress({key: "BridgeValidator", value: bridgeValidator});
_serializeAddress({key: "CrossChainERC20Factory", value: factory});
_serializeAddress({key: "Twin", value: twinBeacon});
_serializeAddress({key: "RelayerOrchestrator", value: relayerOrchestrator});
_serializeAddress({key: "WrappedSol", value: sol});

_postCheck(twinBeacon, factory, bridgeValidator, bridge, relayerOrchestrator, sol);
}

function _postCheck(
address twinBeacon,
address factory,
address bridgeValidator,
address bridge,
address relayerOrchestrator,
address sol
) private view {
// Twin
Twin twinImpl = Twin(payable(UpgradeableBeacon(twinBeacon).implementation()));
require(twinImpl.BRIDGE() == bridge, "PC01: incorrect bridge address in twin impl");

// Factory
UpgradeableBeacon tokenBeacon = UpgradeableBeacon(CrossChainERC20Factory(factory).BEACON());
CrossChainERC20 tokenImpl = CrossChainERC20(tokenBeacon.implementation());
require(tokenImpl.bridge() == bridge, "PC02: incorrect bridge address in token impl");

// BridgeValidator
require(
BridgeValidator(bridgeValidator).BRIDGE() == bridge, "PC03: incorrect bridge address in BridgeValidator"
);
require(
BridgeValidator(bridgeValidator).PARTNER_VALIDATORS() == cfg.partnerValidators,
"PC04: incorrect partnerValidators address in BridgeValidator"
);
require(
BridgeValidator(bridgeValidator).partnerValidatorThreshold() == cfg.partnerValidatorThreshold,
"PC05: incorrect partner validator threshold in BridgeValidator"
);
require(
BridgeValidator(bridgeValidator).getBaseThreshold() == cfg.baseSignatureThreshold,
"PC06: incorrect Base threshold in BridgeValidator"
);
require(
BridgeValidator(bridgeValidator).getBaseValidatorCount() == cfg.baseValidators.length,
"PC07: incorrect registered base validator count"
);

for (uint256 i; i < cfg.baseValidators.length; i++) {
require(
BridgeValidator(bridgeValidator).isBaseValidator(cfg.baseValidators[i]),
"PC08: base validator not registered"
);
}

// Bridge
require(Bridge(bridge).REMOTE_BRIDGE() == cfg.remoteBridge, "PC09: incorrect remote bridge in Bridge contract");
require(Bridge(bridge).TWIN_BEACON() == twinBeacon, "PC10: incorrect twin beacon in Bridge contract");
require(Bridge(bridge).CROSS_CHAIN_ERC20_FACTORY() == factory, "PC11: incorrect factory in Bridge contract");
require(
Bridge(bridge).BRIDGE_VALIDATOR() == bridgeValidator, "PC12: incorrect bridge validator in Bridge contract"
);
require(Bridge(bridge).owner() == cfg.initialOwner, "PC13: incorrect Bridge owner");

for (uint256 i; i < cfg.guardians.length; i++) {
require(
Bridge(bridge).rolesOf(cfg.guardians[i]) == Bridge(bridge).GUARDIAN_ROLE(),
"PC14: guardian missing perms"
);
}

// RelayerOrchestrator
require(
RelayerOrchestrator(relayerOrchestrator).BRIDGE() == bridge, "PC15: incorrect bridge in RelayerOrchestrator"
);
require(
RelayerOrchestrator(relayerOrchestrator).BRIDGE_VALIDATOR() == bridgeValidator,
"PC16: incorrect bridge validator in RelayerOrchestrator"
);

// SOL
require(CrossChainERC20(sol).bridge() == bridge, "PC17: incorrect bridge in SOL contract");
require(LibString.eq(CrossChainERC20(sol).name(), "Solana"), "PC18: incorrect SOL name");
require(LibString.eq(CrossChainERC20(sol).symbol(), "SOL"), "PC19: incorrect SOL symbol");
require(
CrossChainERC20(sol).remoteToken() == Pubkey.unwrap(TokenLib.NATIVE_SOL_PUBKEY),
"PC20: incorrect SOL remote token"
);
require(CrossChainERC20(sol).decimals() == 9, "PC21: incorrect SOL decimals");
}

function _deployTwinBeacon(address precomputedBridgeAddress) private returns (address) {
address twinImpl = address(new Twin(precomputedBridgeAddress));
return address(new UpgradeableBeacon({initialOwner: cfg.initialOwner, initialImplementation: twinImpl}));
}

function _deployFactory(address precomputedBridgeAddress) private returns (address) {
address erc20Impl = address(new CrossChainERC20(precomputedBridgeAddress));
address erc20Beacon =
address(new UpgradeableBeacon({initialOwner: cfg.initialOwner, initialImplementation: erc20Impl}));

address xChainErc20FactoryImpl = address(new CrossChainERC20Factory(erc20Beacon));
return
ERC1967Factory(cfg.erc1967Factory).deploy({implementation: xChainErc20FactoryImpl, admin: cfg.initialOwner});
}

function _deployBridgeValidator(address bridge) private returns (address) {
address bridgeValidatorImpl =
address(new BridgeValidator({bridgeAddress: bridge, partnerValidators: cfg.partnerValidators}));

return ERC1967Factory(cfg.erc1967Factory)
.deployAndCall({
implementation: bridgeValidatorImpl,
admin: cfg.initialOwner,
data: abi.encodeCall(
BridgeValidator.initialize,
(cfg.baseValidators, cfg.baseSignatureThreshold, cfg.partnerValidatorThreshold)
)
});
}

function _deployBridge(address twinBeacon, address crossChainErc20Factory, address bridgeValidator)
private
returns (address)
{
Bridge bridgeImpl = new Bridge({
remoteBridge: cfg.remoteBridge,
twinBeacon: twinBeacon,
crossChainErc20Factory: crossChainErc20Factory,
bridgeValidator: bridgeValidator
});

return ERC1967Factory(cfg.erc1967Factory)
.deployDeterministicAndCall({
implementation: address(bridgeImpl),
admin: cfg.initialOwner,
salt: _salt(),
data: abi.encodeCall(Bridge.initialize, (cfg.initialOwner, cfg.guardians))
});
}

function _deployRelayerOrchestrator(address bridge, address bridgeValidator) private returns (address) {
address relayerOrchestratorImpl =
address(new RelayerOrchestrator({bridge: bridge, bridgeValidator: bridgeValidator}));

return
ERC1967Factory(cfg.erc1967Factory)
.deploy({implementation: relayerOrchestratorImpl, admin: cfg.initialOwner});
}

function _serializeAddress(string memory key, address value) private {
vm.writeJson({
json: LibString.toHexStringChecksummed(value), path: "addresses.json", valueKey: string.concat(".", key)
});
}

function _readAddressFromConfig(string memory key) private view returns (address) {
return vm.parseJsonAddress({json: cfgData, key: string.concat(".", key)});
}

function _readAddressArrayFromConfig(string memory key) private view returns (address[] memory) {
return vm.parseJsonAddressArray({json: cfgData, key: string.concat(".", key)});
}

function _readUintFromConfig(string memory key) private view returns (uint256) {
return vm.parseJsonUint({json: cfgData, key: string.concat(".", key)});
}

function _readBytes32FromConfig(string memory key) private view returns (bytes32) {
return vm.parseJsonBytes32({json: cfgData, key: string.concat(".", key)});
}

/// @dev Appends `msg.sender` to the front of the salt to satisfy a requirement
/// of the `ERC1967Factory.deployDeterministicAndCall()` method.
function _salt() private view returns (bytes32) {
bytes12 s = bytes12(keccak256(abi.encode(cfg.salt)));
return bytes32(abi.encodePacked(msg.sender, s));
}
}