// SPDX-License-Identifier: MIT pragma solidity ^0.8.30; import {LoveScript} from "../script/Love.s.sol"; import {Love} from "../src/Love.sol"; import {Test} from "forge-std/Test.sol"; /// @title LoveCreate2Test /// @notice The deployment address derives from the salt, the creation code and /// the wETH constructor argument. These tests pin that derivation: same /// salt and same wETH give the same address on any chain, a different /// wETH gives a different one. contract LoveCreate2Test is Test { bytes32 constant SALT = keccak256("LOVE"); address constant WETH = 0x4200000000000000000000000000000000000006; LoveScript script; function setUp() public { script = new LoveScript(); // The canonical deterministic deployer, as it exists on live networks. assertGt(CREATE2_FACTORY.code.length, 0, "create2 deployer missing"); } function test_DeploysAtPredictedAddress() public { address predicted = vm.computeCreate2Address(SALT, keccak256(script.initCode(WETH)), CREATE2_FACTORY); Love deployed = Love(_deploy(SALT, WETH)); assertEq(address(deployed), predicted); assertEq(deployed.symbol(), "LOVE"); assertEq(address(deployed.WETH()), WETH); } /// @dev What the deploy script prints must be what the chain gives back. function test_ScriptPredictionMatchesDeployment() public { assertEq(script.predict(SALT, WETH), _deploy(SALT, WETH)); } function test_ScriptUsesKeccakOfLoveAsDefaultSalt() public view { assertEq(script.DEFAULT_SALT(), SALT); } function test_ScriptDefaultsToOpStackWeth() public view { assertEq(script.DEFAULT_WETH(), WETH); } /// @dev Same salt, same code, same wETH, same address. function test_PredictionIsChainAgnostic() public { address onThisChain = script.predict(SALT, WETH); vm.chainId(137); assertEq(script.predict(SALT, WETH), onThisChain); vm.chainId(42_161); assertEq(script.predict(SALT, WETH), onThisChain); } function test_DifferentSaltsGiveDifferentAddresses() public view { assertTrue(script.predict(SALT, WETH) != script.predict(keccak256("LOVE2"), WETH)); } /// @dev The wETH address is part of the creation code, so chains with their /// own wETH get their own token address. function test_DifferentWethGivesDifferentAddress() public view { assertTrue(script.predict(SALT, WETH) != script.predict(SALT, address(0xBEEF))); } /// @dev Redeploying with the same salt must fail, not silently return the /// existing token. function test_RevertWhen_RedeployingWithSameSalt() public { _deploy(SALT, WETH); (bool ok,) = CREATE2_FACTORY.call(abi.encodePacked(SALT, script.initCode(WETH))); assertFalse(ok); } function testFuzz_PredictionMatchesDeployment(bytes32 salt, address weth) public { assertEq(script.predict(salt, weth), _deploy(salt, weth)); } /// @dev Deploys through the canonical deterministic deployer, as the script does. function _deploy(bytes32 salt, address weth) internal returns (address deployed) { (bool ok, bytes memory ret) = CREATE2_FACTORY.call(abi.encodePacked(salt, script.initCode(weth))); require(ok, "create2 deployment failed"); // casting to 'bytes20' is safe because the deployer returns the 20-byte address, raw // forge-lint: disable-next-line(unsafe-typecast) deployed = address(bytes20(ret)); } }