Merge 1-add-main-logic into main
dded29a parent: 69c46a2 modified
CHANGELOG.md +5 -1 | @@ -13,7 +13,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | ||
| 13 | 13 | - `Love`, an ERC-20 token (`LOVE`, 18 decimals) on OpenZeppelin v5, with an |
| 14 | 14 | unrestricted public `mint(address,uint256)` — anyone can mint any amount. |
| 15 | 15 | - Metadata, mint, transfer, approve/`transferFrom`, revert and fuzz tests. |
| 16 | -- Deployment script `script/Love.s.sol`. | |
| 16 | +- Deployment script `script/Love.s.sol`, deploying with CREATE2 through the | |
| 17 | + canonical deterministic deployer so `Love` gets the same address on every EVM | |
| 18 | + network; salt defaults to `keccak256("LOVE")` and is overridable via `SALT`. | |
| 19 | +- `predict()` entrypoint printing the deployment address without broadcasting, | |
| 20 | + and CREATE2 tests pinning address determinism across chain IDs. | |
| 17 | 21 | - `foundry.toml` with pinned solc 0.8.30, optimizer, fmt rules, fuzz/invariant |
| 18 | 22 | defaults, and Base Sepolia RPC/Etherscan endpoints driven by env vars. |
| 19 | 23 | - `remappings.txt` for `@openzeppelin/contracts/` and `forge-std/`. |
| @@ -13,7 +13,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | |||
| 13 | - `Love`, an ERC-20 token (`LOVE`, 18 decimals) on OpenZeppelin v5, with an | 13 | - `Love`, an ERC-20 token (`LOVE`, 18 decimals) on OpenZeppelin v5, with an |
| 14 | unrestricted public `mint(address,uint256)` — anyone can mint any amount. | 14 | unrestricted public `mint(address,uint256)` — anyone can mint any amount. |
| 15 | - Metadata, mint, transfer, approve/`transferFrom`, revert and fuzz tests. | 15 | - Metadata, mint, transfer, approve/`transferFrom`, revert and fuzz tests. |
| 16 | -- Deployment script `script/Love.s.sol`. | 16 | +- Deployment script `script/Love.s.sol`, deploying with CREATE2 through the |
| 17 | + canonical deterministic deployer so `Love` gets the same address on every EVM | ||
| 18 | + network; salt defaults to `keccak256("LOVE")` and is overridable via `SALT`. | ||
| 19 | +- `predict()` entrypoint printing the deployment address without broadcasting, | ||
| 20 | + and CREATE2 tests pinning address determinism across chain IDs. | ||
| 17 | - `foundry.toml` with pinned solc 0.8.30, optimizer, fmt rules, fuzz/invariant | 21 | - `foundry.toml` with pinned solc 0.8.30, optimizer, fmt rules, fuzz/invariant |
| 18 | defaults, and Base Sepolia RPC/Etherscan endpoints driven by env vars. | 22 | defaults, and Base Sepolia RPC/Etherscan endpoints driven by env vars. |
| 19 | - `remappings.txt` for `@openzeppelin/contracts/` and `forge-std/`. | 23 | - `remappings.txt` for `@openzeppelin/contracts/` and `forge-std/`. |
modified
README.md +20 -29 | @@ -1,41 +1,31 @@ | ||
| 1 | -# love | |
| 1 | +# Love | |
| 2 | 2 | |
| 3 | -A [Foundry](https://getfoundry.sh) project. | |
| 3 | +The official LOVE token. | |
| 4 | 4 | |
| 5 | -`Love` (`LOVE`, 18 decimals) is a basic ERC-20 built on | |
| 6 | -[OpenZeppelin](https://github.com/OpenZeppelin/openzeppelin-contracts), with one | |
| 7 | -deliberate twist: `mint` has **no access control**. | |
| 8 | - | |
| 9 | -```solidity | |
| 10 | -function mint(address to, uint256 amount) external; | |
| 11 | -``` | |
| 12 | - | |
| 13 | -Anyone can mint any amount to any address. Supply starts at zero and is | |
| 14 | -unbounded, so the token carries no economic value — it is a faucet / demo token. | |
| 15 | -Do not deploy it to mainnet expecting otherwise. | |
| 16 | - | |
| 17 | -## Setup | |
| 5 | +## Install | |
| 18 | 6 | |
| 19 | 7 | ```shell |
| 20 | 8 | forge install |
| 21 | 9 | cp .env.example .env |
| 22 | 10 | ``` |
| 23 | 11 | |
| 24 | -## Usage | |
| 12 | +## Build & test | |
| 25 | 13 | |
| 26 | 14 | ```shell |
| 27 | 15 | forge build # compile |
| 28 | 16 | forge test # run tests |
| 29 | 17 | forge test -vvv # with traces |
| 30 | 18 | forge fmt # format |
| 31 | -forge coverage # coverage report | |
| 32 | -forge snapshot # gas snapshot | |
| 33 | 19 | anvil # local node |
| 34 | 20 | ``` |
| 35 | 21 | |
| 36 | 22 | ## Deploy |
| 37 | 23 | |
| 24 | +Deployment uses CREATE2, so the token has the same address on every EVM network. | |
| 25 | + | |
| 38 | 26 | ```shell |
| 27 | +forge script script/Love.s.sol:LoveScript --sig 'predict()' # print the address | |
| 28 | + | |
| 39 | 29 | forge script script/Love.s.sol:LoveScript \ |
| 40 | 30 | --rpc-url base_sepolia \ |
| 41 | 31 | --account "$DEPLOYER_ACCOUNT" \ |
| @@ -43,17 +33,6 @@ forge script script/Love.s.sol:LoveScript \ | ||
| 43 | 33 | --verify |
| 44 | 34 | ``` |
| 45 | 35 | |
| 46 | -Drop `--broadcast` for a dry run. | |
| 47 | - | |
| 48 | -## Layout | |
| 49 | - | |
| 50 | -| Path | Contents | | |
| 51 | -| --------- | ------------------- | | |
| 52 | -| `src/` | Contracts | | |
| 53 | -| `test/` | Tests | | |
| 54 | -| `script/` | Deployment scripts | | |
| 55 | -| `lib/` | Dependencies | | |
| 56 | - | |
| 57 | 36 | ## Mint |
| 58 | 37 | |
| 59 | 38 | ```shell |
| @@ -61,3 +40,15 @@ cast send <LOVE_ADDRESS> "mint(address,uint256)" <RECIPIENT> 1000000000000000000 | ||
| 61 | 40 | --rpc-url base_sepolia \ |
| 62 | 41 | --account "$DEPLOYER_ACCOUNT" |
| 63 | 42 | ``` |
| 43 | + | |
| 44 | +## License | |
| 45 | + | |
| 46 | +GPL-3.0 | |
| 47 | + | |
| 48 | +## Contact | |
| 49 | + | |
| 50 | +**Julien Béranger** ([GitHub](https://github.com/julienbrg)) | |
| 51 | + | |
| 52 | +- Element: [@julienbrg:matrix.org](https://matrix.to/#/@julienbrg:matrix.org) | |
| 53 | +- Farcaster: [julien-](https://warpcast.com/julien-) | |
| 54 | +- Telegram: [@julienbrg](https://t.me/julienbrg) | |
| @@ -1,41 +1,31 @@ | |||
| 1 | -# love | 1 | +# Love |
| 2 | 2 | ||
| 3 | -A [Foundry](https://getfoundry.sh) project. | 3 | +The official LOVE token. |
| 4 | 4 | ||
| 5 | -`Love` (`LOVE`, 18 decimals) is a basic ERC-20 built on | 5 | +## Install |
| 6 | -[OpenZeppelin](https://github.com/OpenZeppelin/openzeppelin-contracts), with one | ||
| 7 | -deliberate twist: `mint` has **no access control**. | ||
| 8 | - | ||
| 9 | -```solidity | ||
| 10 | -function mint(address to, uint256 amount) external; | ||
| 11 | -``` | ||
| 12 | - | ||
| 13 | -Anyone can mint any amount to any address. Supply starts at zero and is | ||
| 14 | -unbounded, so the token carries no economic value — it is a faucet / demo token. | ||
| 15 | -Do not deploy it to mainnet expecting otherwise. | ||
| 16 | - | ||
| 17 | -## Setup | ||
| 18 | 6 | ||
| 19 | ```shell | 7 | ```shell |
| 20 | forge install | 8 | forge install |
| 21 | cp .env.example .env | 9 | cp .env.example .env |
| 22 | ``` | 10 | ``` |
| 23 | 11 | ||
| 24 | -## Usage | 12 | +## Build & test |
| 25 | 13 | ||
| 26 | ```shell | 14 | ```shell |
| 27 | forge build # compile | 15 | forge build # compile |
| 28 | forge test # run tests | 16 | forge test # run tests |
| 29 | forge test -vvv # with traces | 17 | forge test -vvv # with traces |
| 30 | forge fmt # format | 18 | forge fmt # format |
| 31 | -forge coverage # coverage report | ||
| 32 | -forge snapshot # gas snapshot | ||
| 33 | anvil # local node | 19 | anvil # local node |
| 34 | ``` | 20 | ``` |
| 35 | 21 | ||
| 36 | ## Deploy | 22 | ## Deploy |
| 37 | 23 | ||
| 24 | +Deployment uses CREATE2, so the token has the same address on every EVM network. | ||
| 25 | + | ||
| 38 | ```shell | 26 | ```shell |
| 27 | +forge script script/Love.s.sol:LoveScript --sig 'predict()' # print the address | ||
| 28 | + | ||
| 39 | forge script script/Love.s.sol:LoveScript \ | 29 | forge script script/Love.s.sol:LoveScript \ |
| 40 | --rpc-url base_sepolia \ | 30 | --rpc-url base_sepolia \ |
| 41 | --account "$DEPLOYER_ACCOUNT" \ | 31 | --account "$DEPLOYER_ACCOUNT" \ |
| @@ -43,17 +33,6 @@ forge script script/Love.s.sol:LoveScript \ | |||
| 43 | --verify | 33 | --verify |
| 44 | ``` | 34 | ``` |
| 45 | 35 | ||
| 46 | -Drop `--broadcast` for a dry run. | ||
| 47 | - | ||
| 48 | -## Layout | ||
| 49 | - | ||
| 50 | -| Path | Contents | | ||
| 51 | -| --------- | ------------------- | | ||
| 52 | -| `src/` | Contracts | | ||
| 53 | -| `test/` | Tests | | ||
| 54 | -| `script/` | Deployment scripts | | ||
| 55 | -| `lib/` | Dependencies | | ||
| 56 | - | ||
| 57 | ## Mint | 36 | ## Mint |
| 58 | 37 | ||
| 59 | ```shell | 38 | ```shell |
| @@ -61,3 +40,15 @@ cast send <LOVE_ADDRESS> "mint(address,uint256)" <RECIPIENT> 1000000000000000000 | |||
| 61 | --rpc-url base_sepolia \ | 40 | --rpc-url base_sepolia \ |
| 62 | --account "$DEPLOYER_ACCOUNT" | 41 | --account "$DEPLOYER_ACCOUNT" |
| 63 | ``` | 42 | ``` |
| 43 | + | ||
| 44 | +## License | ||
| 45 | + | ||
| 46 | +GPL-3.0 | ||
| 47 | + | ||
| 48 | +## Contact | ||
| 49 | + | ||
| 50 | +**Julien Béranger** ([GitHub](https://github.com/julienbrg)) | ||
| 51 | + | ||
| 52 | +- Element: [@julienbrg:matrix.org](https://matrix.to/#/@julienbrg:matrix.org) | ||
| 53 | +- Farcaster: [julien-](https://warpcast.com/julien-) | ||
| 54 | +- Telegram: [@julienbrg](https://t.me/julienbrg) | ||
modified
script/Love.s.sol +50 -3 | @@ -4,18 +4,65 @@ pragma solidity ^0.8.30; | ||
| 4 | 4 | import {Love} from "../src/Love.sol"; |
| 5 | 5 | import {Script, console} from "forge-std/Script.sol"; |
| 6 | 6 | |
| 7 | +/// @notice Deploys `Love` with CREATE2 through the canonical deterministic | |
| 8 | +/// deployer, so the token gets the same address on every EVM network. | |
| 9 | +/// The address depends only on the salt and the creation code — keep | |
| 10 | +/// `solc`, the optimizer settings and `bytecode_hash = "none"` as they | |
| 11 | +/// are in `foundry.toml`, or the address changes. | |
| 7 | 12 | contract LoveScript is Script { |
| 13 | + bytes32 public constant DEFAULT_SALT = keccak256("LOVE"); | |
| 14 | + | |
| 8 | 15 | Love public love; |
| 9 | 16 | |
| 10 | - function run() public { | |
| 17 | + function run() public returns (Love) { | |
| 18 | + bytes32 s = salt(); | |
| 19 | + address predicted = predict(s); | |
| 20 | + | |
| 21 | + console.log("deployer ", CREATE2_FACTORY); | |
| 22 | + console.log("salt ", vm.toString(s)); | |
| 23 | + console.log("initCodeHash ", vm.toString(initCodeHash())); | |
| 24 | + console.log("predicted ", predicted); | |
| 25 | + | |
| 26 | + if (predicted.code.length > 0) { | |
| 27 | + console.log("already deployed, nothing to do"); | |
| 28 | + love = Love(predicted); | |
| 29 | + return love; | |
| 30 | + } | |
| 31 | + | |
| 11 | 32 | vm.startBroadcast(); |
| 33 | + love = new Love{salt: s}(); | |
| 34 | + vm.stopBroadcast(); | |
| 35 | + | |
| 36 | + require(address(love) == predicted, "LoveScript: address mismatch"); | |
| 12 | 37 | |
| 13 | - love = new Love(); | |
| 14 | 38 | console.log("Love deployed at", address(love)); |
| 15 | 39 | console.log("name ", love.name()); |
| 16 | 40 | console.log("symbol ", love.symbol()); |
| 17 | 41 | console.log("totalSupply ", love.totalSupply()); |
| 18 | 42 | |
| 19 | - vm.stopBroadcast(); | |
| 43 | + return love; | |
| 44 | + } | |
| 45 | + | |
| 46 | + /// @notice Print the address `run()` would deploy to, without broadcasting. | |
| 47 | + function predict() public view returns (address predicted) { | |
| 48 | + bytes32 s = salt(); | |
| 49 | + predicted = predict(s); | |
| 50 | + | |
| 51 | + console.log("salt ", vm.toString(s)); | |
| 52 | + console.log("initCodeHash ", vm.toString(initCodeHash())); | |
| 53 | + console.log("predicted ", predicted); | |
| 54 | + } | |
| 55 | + | |
| 56 | + function predict(bytes32 s) public pure returns (address) { | |
| 57 | + return vm.computeCreate2Address(s, initCodeHash(), CREATE2_FACTORY); | |
| 58 | + } | |
| 59 | + | |
| 60 | + function initCodeHash() public pure returns (bytes32) { | |
| 61 | + return keccak256(type(Love).creationCode); | |
| 62 | + } | |
| 63 | + | |
| 64 | + /// @dev `SALT` overrides the default, e.g. to mine a vanity address. | |
| 65 | + function salt() public view returns (bytes32) { | |
| 66 | + return vm.envOr("SALT", DEFAULT_SALT); | |
| 20 | 67 | } |
| 21 | 68 | } |
| @@ -4,18 +4,65 @@ pragma solidity ^0.8.30; | |||
| 4 | import {Love} from "../src/Love.sol"; | 4 | import {Love} from "../src/Love.sol"; |
| 5 | import {Script, console} from "forge-std/Script.sol"; | 5 | import {Script, console} from "forge-std/Script.sol"; |
| 6 | 6 | ||
| 7 | +/// @notice Deploys `Love` with CREATE2 through the canonical deterministic | ||
| 8 | +/// deployer, so the token gets the same address on every EVM network. | ||
| 9 | +/// The address depends only on the salt and the creation code — keep | ||
| 10 | +/// `solc`, the optimizer settings and `bytecode_hash = "none"` as they | ||
| 11 | +/// are in `foundry.toml`, or the address changes. | ||
| 7 | contract LoveScript is Script { | 12 | contract LoveScript is Script { |
| 13 | + bytes32 public constant DEFAULT_SALT = keccak256("LOVE"); | ||
| 14 | + | ||
| 8 | Love public love; | 15 | Love public love; |
| 9 | 16 | ||
| 10 | - function run() public { | 17 | + function run() public returns (Love) { |
| 18 | + bytes32 s = salt(); | ||
| 19 | + address predicted = predict(s); | ||
| 20 | + | ||
| 21 | + console.log("deployer ", CREATE2_FACTORY); | ||
| 22 | + console.log("salt ", vm.toString(s)); | ||
| 23 | + console.log("initCodeHash ", vm.toString(initCodeHash())); | ||
| 24 | + console.log("predicted ", predicted); | ||
| 25 | + | ||
| 26 | + if (predicted.code.length > 0) { | ||
| 27 | + console.log("already deployed, nothing to do"); | ||
| 28 | + love = Love(predicted); | ||
| 29 | + return love; | ||
| 30 | + } | ||
| 31 | + | ||
| 11 | vm.startBroadcast(); | 32 | vm.startBroadcast(); |
| 33 | + love = new Love{salt: s}(); | ||
| 34 | + vm.stopBroadcast(); | ||
| 35 | + | ||
| 36 | + require(address(love) == predicted, "LoveScript: address mismatch"); | ||
| 12 | 37 | ||
| 13 | - love = new Love(); | ||
| 14 | console.log("Love deployed at", address(love)); | 38 | console.log("Love deployed at", address(love)); |
| 15 | console.log("name ", love.name()); | 39 | console.log("name ", love.name()); |
| 16 | console.log("symbol ", love.symbol()); | 40 | console.log("symbol ", love.symbol()); |
| 17 | console.log("totalSupply ", love.totalSupply()); | 41 | console.log("totalSupply ", love.totalSupply()); |
| 18 | 42 | ||
| 19 | - vm.stopBroadcast(); | 43 | + return love; |
| 44 | + } | ||
| 45 | + | ||
| 46 | + /// @notice Print the address `run()` would deploy to, without broadcasting. | ||
| 47 | + function predict() public view returns (address predicted) { | ||
| 48 | + bytes32 s = salt(); | ||
| 49 | + predicted = predict(s); | ||
| 50 | + | ||
| 51 | + console.log("salt ", vm.toString(s)); | ||
| 52 | + console.log("initCodeHash ", vm.toString(initCodeHash())); | ||
| 53 | + console.log("predicted ", predicted); | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + function predict(bytes32 s) public pure returns (address) { | ||
| 57 | + return vm.computeCreate2Address(s, initCodeHash(), CREATE2_FACTORY); | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + function initCodeHash() public pure returns (bytes32) { | ||
| 61 | + return keccak256(type(Love).creationCode); | ||
| 62 | + } | ||
| 63 | + | ||
| 64 | + /// @dev `SALT` overrides the default, e.g. to mine a vanity address. | ||
| 65 | + function salt() public view returns (bytes32) { | ||
| 66 | + return vm.envOr("SALT", DEFAULT_SALT); | ||
| 20 | } | 67 | } |
| 21 | } | 68 | } |
added
test/LoveCreate2.t.sol +76 -0 | new file mode 100644 | ||
| @@ -0,0 +1,76 @@ | ||
| 1 | +// SPDX-License-Identifier: MIT | |
| 2 | +pragma solidity ^0.8.30; | |
| 3 | + | |
| 4 | +import {LoveScript} from "../script/Love.s.sol"; | |
| 5 | +import {Love} from "../src/Love.sol"; | |
| 6 | +import {Test} from "forge-std/Test.sol"; | |
| 7 | + | |
| 8 | +/// @notice The token is meant to live at the same address on every EVM network, | |
| 9 | +/// so these tests pin the CREATE2 deployment path and the inputs the | |
| 10 | +/// address derives from. | |
| 11 | +contract LoveCreate2Test is Test { | |
| 12 | + bytes32 constant SALT = keccak256("LOVE"); | |
| 13 | + | |
| 14 | + LoveScript script; | |
| 15 | + | |
| 16 | + function setUp() public { | |
| 17 | + script = new LoveScript(); | |
| 18 | + | |
| 19 | + // The canonical deterministic deployer, as it exists on live networks. | |
| 20 | + assertGt(CREATE2_FACTORY.code.length, 0, "create2 deployer missing"); | |
| 21 | + } | |
| 22 | + | |
| 23 | + function test_DeploysAtPredictedAddress() public { | |
| 24 | + address predicted = vm.computeCreate2Address(SALT, keccak256(type(Love).creationCode), CREATE2_FACTORY); | |
| 25 | + | |
| 26 | + Love deployed = Love(_deploy(SALT)); | |
| 27 | + | |
| 28 | + assertEq(address(deployed), predicted); | |
| 29 | + assertEq(deployed.symbol(), "LOVE"); | |
| 30 | + } | |
| 31 | + | |
| 32 | + /// @dev What the deploy script prints must be what the chain gives back. | |
| 33 | + function test_ScriptPredictionMatchesDeployment() public { | |
| 34 | + assertEq(script.predict(SALT), _deploy(SALT)); | |
| 35 | + } | |
| 36 | + | |
| 37 | + function test_ScriptUsesKeccakOfLoveAsDefaultSalt() public view { | |
| 38 | + assertEq(script.DEFAULT_SALT(), SALT); | |
| 39 | + } | |
| 40 | + | |
| 41 | + /// @dev Same salt, same code, same address — that is the whole point. | |
| 42 | + function test_PredictionIsChainAgnostic() public { | |
| 43 | + address onThisChain = script.predict(SALT); | |
| 44 | + | |
| 45 | + vm.chainId(137); | |
| 46 | + assertEq(script.predict(SALT), onThisChain); | |
| 47 | + | |
| 48 | + vm.chainId(42_161); | |
| 49 | + assertEq(script.predict(SALT), onThisChain); | |
| 50 | + } | |
| 51 | + | |
| 52 | + function test_DifferentSaltsGiveDifferentAddresses() public view { | |
| 53 | + assertTrue(script.predict(SALT) != script.predict(keccak256("LOVE2"))); | |
| 54 | + } | |
| 55 | + | |
| 56 | + /// @dev Redeploying with the same salt must fail, not silently return the | |
| 57 | + /// existing token. | |
| 58 | + function test_RevertWhen_RedeployingWithSameSalt() public { | |
| 59 | + _deploy(SALT); | |
| 60 | + | |
| 61 | + (bool ok,) = CREATE2_FACTORY.call(abi.encodePacked(SALT, type(Love).creationCode)); | |
| 62 | + assertFalse(ok); | |
| 63 | + } | |
| 64 | + | |
| 65 | + function testFuzz_PredictionMatchesDeployment(bytes32 salt) public { | |
| 66 | + assertEq(script.predict(salt), _deploy(salt)); | |
| 67 | + } | |
| 68 | + | |
| 69 | + function _deploy(bytes32 salt) internal returns (address deployed) { | |
| 70 | + (bool ok, bytes memory ret) = CREATE2_FACTORY.call(abi.encodePacked(salt, type(Love).creationCode)); | |
| 71 | + require(ok, "create2 deployment failed"); | |
| 72 | + // casting to 'bytes20' is safe because the deployer returns the 20-byte address, raw | |
| 73 | + // forge-lint: disable-next-line(unsafe-typecast) | |
| 74 | + deployed = address(bytes20(ret)); | |
| 75 | + } | |
| 76 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,76 @@ | |||
| 1 | +// SPDX-License-Identifier: MIT | ||
| 2 | +pragma solidity ^0.8.30; | ||
| 3 | + | ||
| 4 | +import {LoveScript} from "../script/Love.s.sol"; | ||
| 5 | +import {Love} from "../src/Love.sol"; | ||
| 6 | +import {Test} from "forge-std/Test.sol"; | ||
| 7 | + | ||
| 8 | +/// @notice The token is meant to live at the same address on every EVM network, | ||
| 9 | +/// so these tests pin the CREATE2 deployment path and the inputs the | ||
| 10 | +/// address derives from. | ||
| 11 | +contract LoveCreate2Test is Test { | ||
| 12 | + bytes32 constant SALT = keccak256("LOVE"); | ||
| 13 | + | ||
| 14 | + LoveScript script; | ||
| 15 | + | ||
| 16 | + function setUp() public { | ||
| 17 | + script = new LoveScript(); | ||
| 18 | + | ||
| 19 | + // The canonical deterministic deployer, as it exists on live networks. | ||
| 20 | + assertGt(CREATE2_FACTORY.code.length, 0, "create2 deployer missing"); | ||
| 21 | + } | ||
| 22 | + | ||
| 23 | + function test_DeploysAtPredictedAddress() public { | ||
| 24 | + address predicted = vm.computeCreate2Address(SALT, keccak256(type(Love).creationCode), CREATE2_FACTORY); | ||
| 25 | + | ||
| 26 | + Love deployed = Love(_deploy(SALT)); | ||
| 27 | + | ||
| 28 | + assertEq(address(deployed), predicted); | ||
| 29 | + assertEq(deployed.symbol(), "LOVE"); | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + /// @dev What the deploy script prints must be what the chain gives back. | ||
| 33 | + function test_ScriptPredictionMatchesDeployment() public { | ||
| 34 | + assertEq(script.predict(SALT), _deploy(SALT)); | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + function test_ScriptUsesKeccakOfLoveAsDefaultSalt() public view { | ||
| 38 | + assertEq(script.DEFAULT_SALT(), SALT); | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + /// @dev Same salt, same code, same address — that is the whole point. | ||
| 42 | + function test_PredictionIsChainAgnostic() public { | ||
| 43 | + address onThisChain = script.predict(SALT); | ||
| 44 | + | ||
| 45 | + vm.chainId(137); | ||
| 46 | + assertEq(script.predict(SALT), onThisChain); | ||
| 47 | + | ||
| 48 | + vm.chainId(42_161); | ||
| 49 | + assertEq(script.predict(SALT), onThisChain); | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + function test_DifferentSaltsGiveDifferentAddresses() public view { | ||
| 53 | + assertTrue(script.predict(SALT) != script.predict(keccak256("LOVE2"))); | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + /// @dev Redeploying with the same salt must fail, not silently return the | ||
| 57 | + /// existing token. | ||
| 58 | + function test_RevertWhen_RedeployingWithSameSalt() public { | ||
| 59 | + _deploy(SALT); | ||
| 60 | + | ||
| 61 | + (bool ok,) = CREATE2_FACTORY.call(abi.encodePacked(SALT, type(Love).creationCode)); | ||
| 62 | + assertFalse(ok); | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + function testFuzz_PredictionMatchesDeployment(bytes32 salt) public { | ||
| 66 | + assertEq(script.predict(salt), _deploy(salt)); | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | + function _deploy(bytes32 salt) internal returns (address deployed) { | ||
| 70 | + (bool ok, bytes memory ret) = CREATE2_FACTORY.call(abi.encodePacked(salt, type(Love).creationCode)); | ||
| 71 | + require(ok, "create2 deployment failed"); | ||
| 72 | + // casting to 'bytes20' is safe because the deployer returns the 20-byte address, raw | ||
| 73 | + // forge-lint: disable-next-line(unsafe-typecast) | ||
| 74 | + deployed = address(bytes20(ret)); | ||
| 75 | + } | ||
| 76 | +} | ||