-
Notifications
You must be signed in to change notification settings - Fork 291
feat: set up Base Bridge Base Sepolia deployment task #435
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f5d314c
61cc650
f939464
af877a9
7062242
d2907ba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| OP_COMMIT=594bc933a38425f745b46399a3619bcdeb74965d | ||
| BASE_CONTRACTS_COMMIT=98ec680a67c173d38aa52588c5dc0fbaa1c0561c | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Latest Base contracts commit |
||
| 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@24b6bcaec94eac34524bcd0774dd6ec14dc3bf21 | ||
|
|
||
| .PHONY: deploy | ||
| deploy: | ||
| forge script DeployBridge --rpc-url $(L2_RPC_URL) \ | ||
| --sender $(shell cast wallet address --account testnet-admin) \ | ||
| --account testnet-admin --broadcast -vvvv |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| # Base Bridge Deployment | ||
|
|
||
| Status: EXECUTED | ||
|
|
||
| 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-deployment | ||
| make deps | ||
| ``` | ||
|
|
||
| 2. Connect and unlock Ledger | ||
|
|
||
| 3. Deploy bridge | ||
|
|
||
| ```bash | ||
| make deploy | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| { | ||
| "Bridge": "0x01824a90d32A69022DdAEcC6C5C14Ed08dB4EB9B", | ||
| "BridgeValidator": "0x863Bed3E344035253CC44C75612Ad5fDF5904aEE", | ||
| "CrossChainERC20Factory": "0x488EB7F7cb2568e31595D48cb26F63963Cc7565D", | ||
| "Twin": "0x11bF22cFf007C46C725Dc59A919383326E3cdefB", | ||
| "RelayerOrchestrator": "0x1e0842b2E6FA06A59b05a9c1d36a6480730012CE", | ||
| "WrappedSol": "0xCace0c896714DaF7098FFD8CC54aFCFe0338b4BC" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| { | ||
| "salt": "0x90c21999d061600a9c6553e1e6feddb6c90ba96a617a140fe81e8195be404b6e", | ||
| "initialOwner": "0x5dfEB066334B67355A15dc9b67317fD2a2e1f77f", | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Incident multisig on Sepolia (3-of-14) |
||
| "partnerValidators": "0x0000000000000000000000000000000000000001", | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will upgrade to use correct partnerValidators version when we have it |
||
| "baseValidators": [ | ||
| "0x2880a6DcC8c87dD2874bCBB9ad7E627a407Cf3C2", | ||
| "0xc5fe09f194C01e56fB89cC1155daE033D20cDCc7" | ||
| ], | ||
| "baseSignatureThreshold": 2, | ||
| "partnerValidatorThreshold": 0, | ||
| "remoteBridge": "0x6223fa80b3f80549a9ac4f1deea54e39a74aa8fa8f74a62131460716519d4f8f", | ||
| "guardians": ["0x5dfEB066334B67355A15dc9b67317fD2a2e1f77f"] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Incident multisig on Sepolia (3-of-14) |
||
| } | ||
| 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)); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not relevant, but current OP contracts version commit that we are using on Sepolia