|
| 1 | +// SPDX-License-Identifier: MIT |
| 2 | +pragma solidity ^0.8.24; |
| 3 | + |
| 4 | +import {Script} from "forge-std/Script.sol"; |
| 5 | +import {console2} from "forge-std/console2.sol"; |
| 6 | + |
| 7 | +import {ERC1967Factory} from "solady/utils/ERC1967Factory.sol"; |
| 8 | +import {ERC1967FactoryConstants} from "solady/utils/ERC1967FactoryConstants.sol"; |
| 9 | + |
| 10 | +import {RequestWhitelist} from "../src/RequestWhitelist.sol"; |
| 11 | + |
| 12 | +/// @dev 0age's canonical `ImmutableCreate2Factory`, used to bootstrap the |
| 13 | +/// solady `ERC1967Factory` at its canonical cross-chain address and |
| 14 | +/// to CREATE2-deploy the `RequestWhitelist` implementation if the |
| 15 | +/// target chain is cold. |
| 16 | +interface IImmutableCreate2Factory { |
| 17 | + function safeCreate2(bytes32 salt, bytes calldata initializationCode) external payable returns (address); |
| 18 | +} |
| 19 | + |
| 20 | +/// @title DeployRequestWhitelist |
| 21 | +/// @notice Deterministic proxy deployment of `RequestWhitelist`. |
| 22 | +/// |
| 23 | +/// Every deployed artefact lives at a cross-chain-deterministic |
| 24 | +/// address: |
| 25 | +/// 1. `ERC1967Factory` — always the canonical solady address |
| 26 | +/// (`ERC1967FactoryConstants.ADDRESS`); bootstrapped via |
| 27 | +/// `ImmutableCreate2Factory` if cold. |
| 28 | +/// 2. `RequestWhitelist` implementation — CREATE2'd via |
| 29 | +/// `ImmutableCreate2Factory` at `IMPLEMENTATION_SALT` |
| 30 | +/// (default `bytes32(uint256(0x3f))`). The address is a |
| 31 | +/// function of `(create2-factory, salt, creationCode)` and |
| 32 | +/// is identical on every chain that runs this script. |
| 33 | +/// 3. Proxy — CREATE2'd via the `ERC1967Factory` at the |
| 34 | +/// user-provided `SALT`, initialised atomically in the |
| 35 | +/// same tx (closing the `initialize` front-run window). |
| 36 | +/// |
| 37 | +/// The script is idempotent at the artefact level: if an |
| 38 | +/// artefact already has code at its predicted address, the |
| 39 | +/// deploy step for that artefact is skipped. |
| 40 | +/// |
| 41 | +/// @dev Required env: |
| 42 | +/// - `SALT` bytes32 CREATE2 salt for the proxy. |
| 43 | +/// - `PROXY_ADMIN` address ERC-1967 admin (upgrades). |
| 44 | +/// - `OWNER` address Phase-1 application owner. |
| 45 | +/// - `VALIDATORS` address[],comma-delimited |
| 46 | +/// - `QUORUM` uint 1 <= QUORUM < validators.length |
| 47 | +/// |
| 48 | +/// Optional env: |
| 49 | +/// - `IMPLEMENTATION` address Explicit impl override. |
| 50 | +/// Skips CREATE2 deployment |
| 51 | +/// entirely. |
| 52 | +/// - `IMPLEMENTATION_SALT` bytes32 CREATE2 salt for the impl. |
| 53 | +/// Default `bytes32(uint256(0x3f))`. |
| 54 | +/// Ignored if `IMPLEMENTATION` |
| 55 | +/// is set. |
| 56 | +/// - `COMPLIANCE_OFFICERS` address[],comma-delimited; empty if unset. |
| 57 | +contract DeployRequestWhitelist is Script { |
| 58 | + /// @dev Canonical address of 0age's `ImmutableCreate2Factory` on every |
| 59 | + /// EVM chain where it has been deployed. |
| 60 | + address internal constant IMMUTABLE_CREATE2_FACTORY = 0x0000000000FFe8B47B3e2130213B802212439497; |
| 61 | + |
| 62 | + /// @dev Default CREATE2 salt for the implementation. The top 20 bytes |
| 63 | + /// are zero, so the salt satisfies |
| 64 | + /// `ImmutableCreate2Factory`'s "zero-prefix or caller-prefix" |
| 65 | + /// guard from any deployer. |
| 66 | + bytes32 internal constant DEFAULT_IMPLEMENTATION_SALT = bytes32(uint256(0x3f)); |
| 67 | + |
| 68 | + function run() external returns (ERC1967Factory factory, address implementation, address proxy) { |
| 69 | + bytes32 salt = vm.envBytes32("SALT"); |
| 70 | + address proxyAdmin = vm.envAddress("PROXY_ADMIN"); |
| 71 | + address owner = vm.envAddress("OWNER"); |
| 72 | + uint16 quorumValue = uint16(vm.envUint("QUORUM")); |
| 73 | + address[] memory validatorSet = vm.envAddress("VALIDATORS", ","); |
| 74 | + |
| 75 | + address[] memory complianceOfficers; |
| 76 | + if (vm.envExists("COMPLIANCE_OFFICERS")) { |
| 77 | + complianceOfficers = vm.envAddress("COMPLIANCE_OFFICERS", ","); |
| 78 | + } else { |
| 79 | + complianceOfficers = new address[](0); |
| 80 | + } |
| 81 | + |
| 82 | + address implEnv = vm.envOr("IMPLEMENTATION", address(0)); |
| 83 | + bytes32 implSalt = vm.envOr("IMPLEMENTATION_SALT", DEFAULT_IMPLEMENTATION_SALT); |
| 84 | + |
| 85 | + bytes memory initData = |
| 86 | + abi.encodeCall(RequestWhitelist.initialize, (owner, validatorSet, quorumValue, complianceOfficers)); |
| 87 | + |
| 88 | + vm.startBroadcast(); |
| 89 | + |
| 90 | + factory = _canonicalFactory(); |
| 91 | + implementation = implEnv != address(0) ? implEnv : _deterministicImplementation(implSalt); |
| 92 | + |
| 93 | + address predicted = factory.predictDeterministicAddress(salt); |
| 94 | + proxy = factory.deployDeterministicAndCall(implementation, proxyAdmin, salt, initData); |
| 95 | + |
| 96 | + vm.stopBroadcast(); |
| 97 | + |
| 98 | + require(proxy == predicted, "deterministic address mismatch"); |
| 99 | + |
| 100 | + console2.log("ERC1967Factory:", address(factory)); |
| 101 | + console2.log("Implementation:", implementation); |
| 102 | + console2.log("Proxy: ", proxy); |
| 103 | + } |
| 104 | + |
| 105 | + /// @dev Returns the canonical `ERC1967Factory`, deploying it via |
| 106 | + /// `ImmutableCreate2Factory` if the target chain hasn't seen it |
| 107 | + /// yet. Reverts with a clear message if even the underlying |
| 108 | + /// create2 factory is missing — a fully cold chain needs one |
| 109 | + /// bootstrap step the script cannot do for you. |
| 110 | + function _canonicalFactory() internal returns (ERC1967Factory) { |
| 111 | + address canonical = ERC1967FactoryConstants.ADDRESS; |
| 112 | + if (canonical.code.length != 0) return ERC1967Factory(canonical); |
| 113 | + |
| 114 | + _requireCreate2Factory(); |
| 115 | + address deployed = IImmutableCreate2Factory(IMMUTABLE_CREATE2_FACTORY) |
| 116 | + .safeCreate2(ERC1967FactoryConstants.SALT, ERC1967FactoryConstants.INITCODE); |
| 117 | + require(deployed == canonical, "canonical factory address mismatch"); |
| 118 | + return ERC1967Factory(canonical); |
| 119 | + } |
| 120 | + |
| 121 | + /// @dev Returns the CREATE2-deployed `RequestWhitelist` implementation |
| 122 | + /// at `salt`, deploying it via `ImmutableCreate2Factory` if the |
| 123 | + /// target chain hasn't seen this salt before. Idempotent: a |
| 124 | + /// previously-deployed impl at the predicted address is reused. |
| 125 | + function _deterministicImplementation(bytes32 salt) internal returns (address impl) { |
| 126 | + bytes memory initcode = type(RequestWhitelist).creationCode; |
| 127 | + address predicted = vm.computeCreate2Address(salt, keccak256(initcode), IMMUTABLE_CREATE2_FACTORY); |
| 128 | + if (predicted.code.length != 0) return predicted; |
| 129 | + |
| 130 | + _requireCreate2Factory(); |
| 131 | + impl = IImmutableCreate2Factory(IMMUTABLE_CREATE2_FACTORY).safeCreate2(salt, initcode); |
| 132 | + require(impl == predicted, "implementation address mismatch"); |
| 133 | + } |
| 134 | + |
| 135 | + function _requireCreate2Factory() internal view { |
| 136 | + require( |
| 137 | + IMMUTABLE_CREATE2_FACTORY.code.length != 0, |
| 138 | + "ImmutableCreate2Factory not deployed; bootstrap it first (see 0age/ImmutableCreate2Factory)" |
| 139 | + ); |
| 140 | + } |
| 141 | +} |
0 commit comments