julien/lovepublic Fork 0
main
Commits
Clone
git clone https://git.rickub.com/julien/love.git
git clone ssh://git@rickub.com/julien/love.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Merge 5-weth-bytecode-verification into main 2f6da5e · on main · rickub · 45m ago
CHANGELOG.md · 74 lines · 4.6 KBmarkdown
Blame HistoryOpen raw

Changelog

All notable changes to this project are documented in this file.

The format is based on Keep a Changelog,
and this project adheres to Semantic Versioning.

[Unreleased]

Changed

  • Love's constructor no longer takes wETH. It reads it from WETHRegistry at
    a fixed address and reverts with RegistryNotDeployed or WethNotRegistered
    if there is nothing to read. The creation code is therefore byte-identical on
    every chain, so CREATE2 gives LOVE one address everywhere rather than one per
    wETH deployment — the previous behaviour, where the wETH argument was part of
    the creation code, is gone. Breaking: the deployment interface and the
    resulting address both change.
  • script/Love.s.sol brings a chain up in three steps — deploy the registry,
    register wETH, deploy the token — skipping whichever has already happened.
    predict() and initCode() no longer take a wETH argument.
  • script/multichain-check.sh now gives its two anvil nodes different wETH
    implementations at different addresses, which is what makes a matching LOVE
    address meaningful; it also fails if both nodes end up on the same wETH.
  • Tests run against real WETH9 runtime bytecode etched from live chains rather
    than against MockWETH, since a mock's codehash is not on the allowlist and
    adding it would mean testing a different allowlist from the one that ships.
    MockWETH is now the "correct behaviour, wrong bytecode" rejection case.

Added

  • WETHRegistry, naming the wETH LOVE pegs to on a chain. register() is
    permissionless and payable, takes exactly 1 wei, and accepts a candidate only
    if its EXTCODEHASH is one of ten reviewed wETH implementations and a
    deposit/withdraw round trip of that wei succeeds. Ownerless, no setter,
    write-once, so nothing can move the token under a live peg. The allowlist is
    compile-time, so extending it means publishing a new registry and no key can
    revoke an entry. The probe wei is kept rather than refunded, so a registrar
    with no payable fallback can still register. receive() reverts outside a
    probe, keeping the registry from becoming ether it cannot move.
  • IWETH, the narrow wrapper interface the registry probes against.
  • script/weth-codehashes.sh and script/weth-chains.tsv, surveying the wETH
    on 27 chains and grouping them by implementation, so every allowlist entry is
    reproducible from chain state. The survey found 23 distinct codehashes across
    those chains, collapsing to 5 implementations over 14 chains once bridged-ETH
    tokens, upgradeable proxies and non-EVM bytecode are excluded.
  • test/fixtures/, real WETH9 runtime bytecode from Base and Ethereum, with
    test/Fixtures.sol to stage a chain: registry, wETH, registration.
  • WETHRegistryTest, covering both gates, the write-once rule, the exact probe
    value, ether sent outside a probe, and all ten allowlist entries.
  • Foundry project scaffolding (forge-std, CI workflow, .gitignore).
  • Love, an ERC-20 token (LOVE, 18 decimals) on OpenZeppelin v5, pegged to
    wETH at 100000 LOVE per wETH. No mint function and no owner: supply moves
    only through the permissionless deposit(uint256) and withdraw(uint256),
    so circulating LOVE is always backed by wETH held by the contract.
    withdraw reverts with AmountNotDivisibleByRate unless the amount is a
    multiple of the rate, keeping the peg free of rounding dust.
  • MockWETH, a test-only WETH9 stand-in, and tests covering deposit, withdraw,
    the divisibility revert, backing invariants, round-trips, transfers,
    approve/transferFrom, reverts and fuzzing.
  • Deployment script script/Love.s.sol, deploying with CREATE2 through the
    canonical deterministic deployer; salt defaults to keccak256("LOVE") and is
    overridable via SALT, the wETH to register defaults to the OP-Stack address
    and is overridable via WETH. Neither contract takes constructor arguments,
    so both land at the same address on every chain.
  • predict() entrypoint printing the deployment address without broadcasting,
    and CREATE2 tests pinning address determinism across chain IDs, including
    that two chains with different wETH still give LOVE one address.
  • foundry.toml with pinned solc 0.8.30, optimizer, fmt rules, fuzz/invariant
    defaults, and Base Sepolia RPC/Etherscan endpoints driven by env vars.
  • script/multichain-check.sh, deploying Love on two local anvil nodes with
    different chain IDs and failing if the two addresses don't match.
  • remappings.txt for @openzeppelin/contracts/ and forge-std/.
  • .env.example documenting required environment variables, including WETH.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# Changelog

All notable changes to this project are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Changed

- `Love`'s constructor no longer takes wETH. It reads it from `WETHRegistry` at
  a fixed address and reverts with `RegistryNotDeployed` or `WethNotRegistered`
  if there is nothing to read. The creation code is therefore byte-identical on
  every chain, so CREATE2 gives LOVE one address everywhere rather than one per
  wETH deployment — the previous behaviour, where the wETH argument was part of
  the creation code, is gone. **Breaking:** the deployment interface and the
  resulting address both change.
- `script/Love.s.sol` brings a chain up in three steps — deploy the registry,
  register wETH, deploy the token — skipping whichever has already happened.
  `predict()` and `initCode()` no longer take a wETH argument.
- `script/multichain-check.sh` now gives its two anvil nodes different wETH
  implementations at different addresses, which is what makes a matching LOVE
  address meaningful; it also fails if both nodes end up on the same wETH.
- Tests run against real WETH9 runtime bytecode etched from live chains rather
  than against `MockWETH`, since a mock's codehash is not on the allowlist and
  adding it would mean testing a different allowlist from the one that ships.
  `MockWETH` is now the "correct behaviour, wrong bytecode" rejection case.

### Added

- `WETHRegistry`, naming the wETH LOVE pegs to on a chain. `register()` is
  permissionless and payable, takes exactly 1 wei, and accepts a candidate only
  if its `EXTCODEHASH` is one of ten reviewed wETH implementations and a
  deposit/withdraw round trip of that wei succeeds. Ownerless, no setter,
  write-once, so nothing can move the token under a live peg. The allowlist is
  compile-time, so extending it means publishing a new registry and no key can
  revoke an entry. The probe wei is kept rather than refunded, so a registrar
  with no payable fallback can still register. `receive()` reverts outside a
  probe, keeping the registry from becoming ether it cannot move.
- `IWETH`, the narrow wrapper interface the registry probes against.
- `script/weth-codehashes.sh` and `script/weth-chains.tsv`, surveying the wETH
  on 27 chains and grouping them by implementation, so every allowlist entry is
  reproducible from chain state. The survey found 23 distinct codehashes across
  those chains, collapsing to 5 implementations over 14 chains once bridged-ETH
  tokens, upgradeable proxies and non-EVM bytecode are excluded.
- `test/fixtures/`, real WETH9 runtime bytecode from Base and Ethereum, with
  `test/Fixtures.sol` to stage a chain: registry, wETH, registration.
- `WETHRegistryTest`, covering both gates, the write-once rule, the exact probe
  value, ether sent outside a probe, and all ten allowlist entries.
- Foundry project scaffolding (`forge-std`, CI workflow, `.gitignore`).
- `Love`, an ERC-20 token (`LOVE`, 18 decimals) on OpenZeppelin v5, pegged to
  wETH at 100000 LOVE per wETH. No mint function and no owner: supply moves
  only through the permissionless `deposit(uint256)` and `withdraw(uint256)`,
  so circulating LOVE is always backed by wETH held by the contract.
  `withdraw` reverts with `AmountNotDivisibleByRate` unless the amount is a
  multiple of the rate, keeping the peg free of rounding dust.
- `MockWETH`, a test-only WETH9 stand-in, and tests covering deposit, withdraw,
  the divisibility revert, backing invariants, round-trips, transfers,
  approve/`transferFrom`, reverts and fuzzing.
- Deployment script `script/Love.s.sol`, deploying with CREATE2 through the
  canonical deterministic deployer; salt defaults to `keccak256("LOVE")` and is
  overridable via `SALT`, the wETH to register defaults to the OP-Stack address
  and is overridable via `WETH`. Neither contract takes constructor arguments,
  so both land at the same address on every chain.
- `predict()` entrypoint printing the deployment address without broadcasting,
  and CREATE2 tests pinning address determinism across chain IDs, including
  that two chains with different wETH still give LOVE one address.
- `foundry.toml` with pinned solc 0.8.30, optimizer, fmt rules, fuzz/invariant
  defaults, and Base Sepolia RPC/Etherscan endpoints driven by env vars.
- `script/multichain-check.sh`, deploying `Love` on two local anvil nodes with
  different chain IDs and failing if the two addresses don't match.
- `remappings.txt` for `@openzeppelin/contracts/` and `forge-std/`.
- `.env.example` documenting required environment variables, including `WETH`.