// SPDX-License-Identifier: MIT pragma solidity ^0.8.30; import {IWETH} from "../src/IWETH.sol"; import {Love} from "../src/Love.sol"; import {WETHRegistry} from "../src/WETHRegistry.sol"; import {Script, console} from "forge-std/Script.sol"; /// @title LoveScript /// @author Julien Béranger /// @notice Brings LOVE up on a chain, in the order the design requires: /// registry first, wETH registered second, token third. /// @dev Both contracts go through the canonical deterministic deployer and /// neither takes constructor arguments, so their creation code is /// identical on every chain and so are their addresses. That is the whole /// point — LOVE is one address everywhere, not one per wETH deployment. /// Keep `solc`, the optimizer settings and `bytecode_hash = "none"` as /// they are in `foundry.toml`, or both addresses move. contract LoveScript is Script { /// @notice Salt used for `Love` when `SALT` is not set in the environment. bytes32 public constant DEFAULT_SALT = keccak256("LOVE"); /// @notice Salt the registry is deployed with. Not configurable: `Love` /// has the resulting address compiled into it. bytes32 public constant REGISTRY_SALT = keccak256("LOVE.WETHRegistry"); /// @notice wETH address used when `WETH` is not set in the environment. /// @dev The OP Stack predeploy, which is where wETH sits on Optimism, /// Base, Mode, Zora, Lisk, World Chain, Unichain, Soneium and Ink. /// Chains outside that set need `WETH` set explicitly. address public constant DEFAULT_WETH = 0x4200000000000000000000000000000000000006; /// @notice The registry, once `run()` has deployed or found it. WETHRegistry public registry; /// @notice The token, once `run()` has deployed or found it. Love public love; /// @notice Deploy the registry, register wETH and deploy the token, /// skipping whichever of those has already happened. /// @dev Reads `SALT` and `WETH` from the environment, falling back to the /// defaults, and asserts both deployments match their predictions. /// @return The deployed token. function run() public returns (Love) { address w = wethAddress(); address predictedRegistry = registryAddress(); address predicted = predict(salt()); console.log("deployer ", CREATE2_FACTORY); console.log("registry ", predictedRegistry); console.log("weth ", w); console.log("salt ", vm.toString(salt())); console.log("initCodeHash ", vm.toString(initCodeHash())); console.log("predicted ", predicted); _ensureRegistry(predictedRegistry); _ensureRegistered(IWETH(w)); _ensureLove(predicted); return love; } /// @notice Print what `run()` would deploy, without broadcasting. /// @return predicted The address the current salt derives to. function predict() public view returns (address predicted) { predicted = predict(salt()); console.log("registry ", registryAddress()); console.log("weth ", wethAddress()); console.log("salt ", vm.toString(salt())); console.log("initCodeHash ", vm.toString(initCodeHash())); console.log("predicted ", predicted); } /// @notice Derive the token's deployment address from a salt. /// @dev Takes no wETH argument, unlike the address derivation this /// replaces: wETH is no longer in the creation code, which is why the /// answer is the same on every chain. /// @param s The CREATE2 salt. /// @return The address `Love` would land at. function predict(bytes32 s) public pure returns (address) { return vm.computeCreate2Address(s, initCodeHash(), CREATE2_FACTORY); } /// @notice The address the registry lands at on every chain. /// @return The registry's CREATE2 address. function registryAddress() public pure returns (address) { return vm.computeCreate2Address(REGISTRY_SALT, keccak256(registryInitCode()), CREATE2_FACTORY); } /// @notice The token's creation code, which now carries no arguments. /// @return The creation code handed to CREATE2. function initCode() public pure returns (bytes memory) { return type(Love).creationCode; } /// @notice Hash of the token's creation code. /// @return The keccak256 of `initCode()`. function initCodeHash() public pure returns (bytes32) { return keccak256(initCode()); } /// @notice The registry's creation code. /// @return The creation code handed to CREATE2. function registryInitCode() public pure returns (bytes memory) { return type(WETHRegistry).creationCode; } /// @notice The salt to deploy the token with. /// @dev `SALT` overrides the default, e.g. to mine a vanity address or to /// route around a chain where the registry has been squatted. /// @return The configured salt, or `DEFAULT_SALT`. function salt() public view returns (bytes32) { return vm.envOr("SALT", DEFAULT_SALT); } /// @notice The wETH address to register on this chain. /// @dev `WETH` overrides the default, which only holds on OP Stack chains. /// Ignored once a wETH is already registered. /// @return The configured wETH address, or `DEFAULT_WETH`. function wethAddress() public view returns (address) { return vm.envOr("WETH", DEFAULT_WETH); } /// @dev Deploys the registry unless it is already there. /// @param predicted Where it should land. function _ensureRegistry(address predicted) private { if (predicted.code.length > 0) { console.log("registry already deployed"); registry = WETHRegistry(payable(predicted)); return; } vm.startBroadcast(); registry = new WETHRegistry{salt: REGISTRY_SALT}(); vm.stopBroadcast(); require(address(registry) == predicted, "LoveScript: registry address mismatch"); console.log("registry deployed at", address(registry)); } /// @dev Registers `candidate` unless a wETH is already registered. Reverts /// inside the registry if the candidate's code is not a reviewed wETH /// — which is the intended outcome on a chain whose wETH has not been /// reviewed, not a script failure to work around. /// @param candidate The wETH to register. function _ensureRegistered(IWETH candidate) private { IWETH registered = registry.weth(); if (address(registered) != address(0)) { console.log("weth already registered", address(registered)); require( address(registered) == address(candidate) || address(candidate) == DEFAULT_WETH, "LoveScript: a different weth is registered on this chain" ); return; } vm.startBroadcast(); registry.register{value: registry.PROBE()}(candidate); vm.stopBroadcast(); console.log("weth registered", address(registry.weth())); } /// @dev Deploys the token unless it is already there. /// @param predicted Where it should land. function _ensureLove(address predicted) private { if (predicted.code.length > 0) { console.log("already deployed, nothing to do"); love = Love(predicted); return; } vm.startBroadcast(); love = new Love{salt: salt()}(); vm.stopBroadcast(); require(address(love) == predicted, "LoveScript: address mismatch"); console.log("Love deployed at", address(love)); console.log("name ", love.name()); console.log("symbol ", love.symbol()); console.log("weth ", address(love.WETH())); console.log("rate ", love.RATE()); console.log("totalSupply ", love.totalSupply()); } }