julien/lovepublic Fork 0
ffa4716
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.

read weth from the registry instead of a constructor arg

julienbrg committed 2026-09-17T23:33:29+02:00 Browse files
ffa4716 parent: 27d0645
modified foundry.toml +4 -1
@@ -11,7 +11,10 @@ optimizer_runs = 200
1111 via_ir = false
1212 bytecode_hash = "none"
1313 verbosity = 2
14-fs_permissions = [{ access = "read", path = "./out" }]
14+fs_permissions = [
15+ { access = "read", path = "./out" },
16+ { access = "read", path = "./test/fixtures" },
17+]
1518
1619 [profile.ci]
1720 fuzz = { runs = 10_000 }
@@ -11,7 +11,10 @@ optimizer_runs = 200
11 via_ir = false11 via_ir = false
12 bytecode_hash = "none"12 bytecode_hash = "none"
13 verbosity = 213 verbosity = 2
14-fs_permissions = [{ access = "read", path = "./out" }]14+fs_permissions = [
15+ { access = "read", path = "./out" },
16+ { access = "read", path = "./test/fixtures" },
17+]
15 18
16 [profile.ci]19 [profile.ci]
17 fuzz = { runs = 10_000 }20 fuzz = { runs = 10_000 }
modified script/Love.s.sol +135 -59
@@ -1,112 +1,188 @@
11 // SPDX-License-Identifier: MIT
22 pragma solidity ^0.8.30;
33
4+import {IWETH} from "../src/IWETH.sol";
45 import {Love} from "../src/Love.sol";
5-import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
6+import {WETHRegistry} from "../src/WETHRegistry.sol";
67 import {Script, console} from "forge-std/Script.sol";
78
89 /// @title LoveScript
910 /// @author Julien Béranger
10-/// @notice Deploys `Love` with CREATE2 through the canonical deterministic
11-/// deployer. The address depends on the salt and the creation code, and
12-/// the creation code embeds the wETH address — so the token only gets
13-/// the same address on chains where wETH sits at the same address. Keep
14-/// `solc`, the optimizer settings and `bytecode_hash = "none"` as they
15-/// are in `foundry.toml`, or the address changes too.
11+/// @notice Brings LOVE up on a chain, in the order the design requires:
12+/// registry first, wETH registered second, token third.
13+/// @dev Both contracts go through the canonical deterministic deployer and
14+/// neither takes constructor arguments, so their creation code is
15+/// identical on every chain and so are their addresses. That is the whole
16+/// point — LOVE is one address everywhere, not one per wETH deployment.
17+/// Keep `solc`, the optimizer settings and `bytecode_hash = "none"` as
18+/// they are in `foundry.toml`, or both addresses move.
1619 contract LoveScript is Script {
17- /// @notice Salt used when `SALT` is not set in the environment.
20+ /// @notice Salt used for `Love` when `SALT` is not set in the environment.
1821 bytes32 public constant DEFAULT_SALT = keccak256("LOVE");
1922
23+ /// @notice Salt the registry is deployed with. Not configurable: `Love`
24+ /// has the resulting address compiled into it.
25+ bytes32 public constant REGISTRY_SALT = keccak256("LOVE.WETHRegistry");
26+
2027 /// @notice wETH address used when `WETH` is not set in the environment.
21- /// @dev wETH on Base, Optimism and every other OP-Stack chain.
28+ /// @dev The OP Stack predeploy, which is where wETH sits on Optimism,
29+ /// Base, Mode, Zora, Lisk, World Chain, Unichain, Soneium and Ink.
30+ /// Chains outside that set need `WETH` set explicitly.
2231 address public constant DEFAULT_WETH = 0x4200000000000000000000000000000000000006;
2332
33+ /// @notice The registry, once `run()` has deployed or found it.
34+ WETHRegistry public registry;
35+
2436 /// @notice The token, once `run()` has deployed or found it.
2537 Love public love;
2638
27- /// @notice Deploy `Love`, or return it untouched if it is already there.
39+ /// @notice Deploy the registry, register wETH and deploy the token,
40+ /// skipping whichever of those has already happened.
2841 /// @dev Reads `SALT` and `WETH` from the environment, falling back to the
29- /// defaults, and asserts the deployed address matches the prediction.
42+ /// defaults, and asserts both deployments match their predictions.
3043 /// @return The deployed token.
3144 function run() public returns (Love) {
32- bytes32 s = salt();
3345 address w = wethAddress();
34- address predicted = predict(s, w);
46+ address predictedRegistry = registryAddress();
47+ address predicted = predict(salt());
3548
3649 console.log("deployer ", CREATE2_FACTORY);
37- console.log("salt ", vm.toString(s));
50+ console.log("registry ", predictedRegistry);
3851 console.log("weth ", w);
39- console.log("initCodeHash ", vm.toString(initCodeHash(w)));
52+ console.log("salt ", vm.toString(salt()));
53+ console.log("initCodeHash ", vm.toString(initCodeHash()));
4054 console.log("predicted ", predicted);
4155
42- if (predicted.code.length > 0) {
43- console.log("already deployed, nothing to do");
44- love = Love(predicted);
45- return love;
46- }
47-
48- vm.startBroadcast();
49- love = new Love{salt: s}(IERC20(w));
50- vm.stopBroadcast();
51-
52- require(address(love) == predicted, "LoveScript: address mismatch");
53-
54- console.log("Love deployed at", address(love));
55- console.log("name ", love.name());
56- console.log("symbol ", love.symbol());
57- console.log("weth ", address(love.WETH()));
58- console.log("rate ", love.RATE());
59- console.log("totalSupply ", love.totalSupply());
56+ _ensureRegistry(predictedRegistry);
57+ _ensureRegistered(IWETH(w));
58+ _ensureLove(predicted);
6059
6160 return love;
6261 }
6362
64- /// @notice Print the address `run()` would deploy to, without broadcasting.
65- /// @return predicted The address the current salt and wETH derive to.
63+ /// @notice Print what `run()` would deploy, without broadcasting.
64+ /// @return predicted The address the current salt derives to.
6665 function predict() public view returns (address predicted) {
67- bytes32 s = salt();
68- address w = wethAddress();
69- predicted = predict(s, w);
66+ predicted = predict(salt());
7067
71- console.log("salt ", vm.toString(s));
72- console.log("weth ", w);
73- console.log("initCodeHash ", vm.toString(initCodeHash(w)));
68+ console.log("registry ", registryAddress());
69+ console.log("weth ", wethAddress());
70+ console.log("salt ", vm.toString(salt()));
71+ console.log("initCodeHash ", vm.toString(initCodeHash()));
7472 console.log("predicted ", predicted);
7573 }
7674
77- /// @notice Derive the deployment address from a salt and a wETH address.
75+ /// @notice Derive the token's deployment address from a salt.
76+ /// @dev Takes no wETH argument, unlike the address derivation this
77+ /// replaces: wETH is no longer in the creation code, which is why the
78+ /// answer is the same on every chain.
7879 /// @param s The CREATE2 salt.
79- /// @param w The wETH the token would be pegged to.
8080 /// @return The address `Love` would land at.
81- function predict(bytes32 s, address w) public pure returns (address) {
82- return vm.computeCreate2Address(s, initCodeHash(w), CREATE2_FACTORY);
81+ function predict(bytes32 s) public pure returns (address) {
82+ return vm.computeCreate2Address(s, initCodeHash(), CREATE2_FACTORY);
83+ }
84+
85+ /// @notice The address the registry lands at on every chain.
86+ /// @return The registry's CREATE2 address.
87+ function registryAddress() public pure returns (address) {
88+ return vm.computeCreate2Address(REGISTRY_SALT, keccak256(registryInitCode()), CREATE2_FACTORY);
8389 }
8490
85- /// @notice The creation code CREATE2 is handed, constructor argument included.
86- /// @param w The wETH the token would be pegged to.
87- /// @return The creation code, with `w` ABI-encoded onto it.
88- function initCode(address w) public pure returns (bytes memory) {
89- return abi.encodePacked(type(Love).creationCode, abi.encode(w));
91+ /// @notice The token's creation code, which now carries no arguments.
92+ /// @return The creation code handed to CREATE2.
93+ function initCode() public pure returns (bytes memory) {
94+ return type(Love).creationCode;
9095 }
9196
92- /// @notice Hash of the creation code, the second input to the address derivation.
93- /// @param w The wETH the token would be pegged to.
94- /// @return The keccak256 of `initCode(w)`.
95- function initCodeHash(address w) public pure returns (bytes32) {
96- return keccak256(initCode(w));
97+ /// @notice Hash of the token's creation code.
98+ /// @return The keccak256 of `initCode()`.
99+ function initCodeHash() public pure returns (bytes32) {
100+ return keccak256(initCode());
97101 }
98102
99- /// @notice The salt to deploy with.
100- /// @dev `SALT` overrides the default, e.g. to mine a vanity address.
103+ /// @notice The registry's creation code.
104+ /// @return The creation code handed to CREATE2.
105+ function registryInitCode() public pure returns (bytes memory) {
106+ return type(WETHRegistry).creationCode;
107+ }
108+
109+ /// @notice The salt to deploy the token with.
110+ /// @dev `SALT` overrides the default, e.g. to mine a vanity address or to
111+ /// route around a chain where the registry has been squatted.
101112 /// @return The configured salt, or `DEFAULT_SALT`.
102113 function salt() public view returns (bytes32) {
103114 return vm.envOr("SALT", DEFAULT_SALT);
104115 }
105116
106- /// @notice The wETH address to peg the deployment to.
107- /// @dev `WETH` overrides the default, which only holds on OP-Stack chains.
117+ /// @notice The wETH address to register on this chain.
118+ /// @dev `WETH` overrides the default, which only holds on OP Stack chains.
119+ /// Ignored once a wETH is already registered.
108120 /// @return The configured wETH address, or `DEFAULT_WETH`.
109121 function wethAddress() public view returns (address) {
110122 return vm.envOr("WETH", DEFAULT_WETH);
111123 }
124+
125+ /// @dev Deploys the registry unless it is already there.
126+ /// @param predicted Where it should land.
127+ function _ensureRegistry(address predicted) private {
128+ if (predicted.code.length > 0) {
129+ console.log("registry already deployed");
130+ registry = WETHRegistry(payable(predicted));
131+ return;
132+ }
133+
134+ vm.startBroadcast();
135+ registry = new WETHRegistry{salt: REGISTRY_SALT}();
136+ vm.stopBroadcast();
137+
138+ require(address(registry) == predicted, "LoveScript: registry address mismatch");
139+ console.log("registry deployed at", address(registry));
140+ }
141+
142+ /// @dev Registers `candidate` unless a wETH is already registered. Reverts
143+ /// inside the registry if the candidate's code is not a reviewed wETH
144+ /// — which is the intended outcome on a chain whose wETH has not been
145+ /// reviewed, not a script failure to work around.
146+ /// @param candidate The wETH to register.
147+ function _ensureRegistered(IWETH candidate) private {
148+ IWETH registered = registry.weth();
149+
150+ if (address(registered) != address(0)) {
151+ console.log("weth already registered", address(registered));
152+ require(
153+ address(registered) == address(candidate) || address(candidate) == DEFAULT_WETH,
154+ "LoveScript: a different weth is registered on this chain"
155+ );
156+ return;
157+ }
158+
159+ vm.startBroadcast();
160+ registry.register{value: registry.PROBE()}(candidate);
161+ vm.stopBroadcast();
162+
163+ console.log("weth registered", address(registry.weth()));
164+ }
165+
166+ /// @dev Deploys the token unless it is already there.
167+ /// @param predicted Where it should land.
168+ function _ensureLove(address predicted) private {
169+ if (predicted.code.length > 0) {
170+ console.log("already deployed, nothing to do");
171+ love = Love(predicted);
172+ return;
173+ }
174+
175+ vm.startBroadcast();
176+ love = new Love{salt: salt()}();
177+ vm.stopBroadcast();
178+
179+ require(address(love) == predicted, "LoveScript: address mismatch");
180+
181+ console.log("Love deployed at", address(love));
182+ console.log("name ", love.name());
183+ console.log("symbol ", love.symbol());
184+ console.log("weth ", address(love.WETH()));
185+ console.log("rate ", love.RATE());
186+ console.log("totalSupply ", love.totalSupply());
187+ }
112188 }
@@ -1,112 +1,188 @@
1 // SPDX-License-Identifier: MIT1 // SPDX-License-Identifier: MIT
2 pragma solidity ^0.8.30;2 pragma solidity ^0.8.30;
3 3
4+import {IWETH} from "../src/IWETH.sol";
4 import {Love} from "../src/Love.sol";5 import {Love} from "../src/Love.sol";
5-import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";6+import {WETHRegistry} from "../src/WETHRegistry.sol";
6 import {Script, console} from "forge-std/Script.sol";7 import {Script, console} from "forge-std/Script.sol";
7 8
8 /// @title LoveScript9 /// @title LoveScript
9 /// @author Julien Béranger10 /// @author Julien Béranger
10-/// @notice Deploys `Love` with CREATE2 through the canonical deterministic11+/// @notice Brings LOVE up on a chain, in the order the design requires:
11-/// deployer. The address depends on the salt and the creation code, and12+/// registry first, wETH registered second, token third.
12-/// the creation code embeds the wETH address — so the token only gets13+/// @dev Both contracts go through the canonical deterministic deployer and
13-/// the same address on chains where wETH sits at the same address. Keep14+/// neither takes constructor arguments, so their creation code is
14-/// `solc`, the optimizer settings and `bytecode_hash = "none"` as they15+/// identical on every chain and so are their addresses. That is the whole
15-/// are in `foundry.toml`, or the address changes too.16+/// point — LOVE is one address everywhere, not one per wETH deployment.
17+/// Keep `solc`, the optimizer settings and `bytecode_hash = "none"` as
18+/// they are in `foundry.toml`, or both addresses move.
16 contract LoveScript is Script {19 contract LoveScript is Script {
17- /// @notice Salt used when `SALT` is not set in the environment.20+ /// @notice Salt used for `Love` when `SALT` is not set in the environment.
18 bytes32 public constant DEFAULT_SALT = keccak256("LOVE");21 bytes32 public constant DEFAULT_SALT = keccak256("LOVE");
19 22
23+ /// @notice Salt the registry is deployed with. Not configurable: `Love`
24+ /// has the resulting address compiled into it.
25+ bytes32 public constant REGISTRY_SALT = keccak256("LOVE.WETHRegistry");
26+
20 /// @notice wETH address used when `WETH` is not set in the environment.27 /// @notice wETH address used when `WETH` is not set in the environment.
21- /// @dev wETH on Base, Optimism and every other OP-Stack chain.28+ /// @dev The OP Stack predeploy, which is where wETH sits on Optimism,
29+ /// Base, Mode, Zora, Lisk, World Chain, Unichain, Soneium and Ink.
30+ /// Chains outside that set need `WETH` set explicitly.
22 address public constant DEFAULT_WETH = 0x4200000000000000000000000000000000000006;31 address public constant DEFAULT_WETH = 0x4200000000000000000000000000000000000006;
23 32
33+ /// @notice The registry, once `run()` has deployed or found it.
34+ WETHRegistry public registry;
35+
24 /// @notice The token, once `run()` has deployed or found it.36 /// @notice The token, once `run()` has deployed or found it.
25 Love public love;37 Love public love;
26 38
27- /// @notice Deploy `Love`, or return it untouched if it is already there.39+ /// @notice Deploy the registry, register wETH and deploy the token,
40+ /// skipping whichever of those has already happened.
28 /// @dev Reads `SALT` and `WETH` from the environment, falling back to the41 /// @dev Reads `SALT` and `WETH` from the environment, falling back to the
29- /// defaults, and asserts the deployed address matches the prediction.42+ /// defaults, and asserts both deployments match their predictions.
30 /// @return The deployed token.43 /// @return The deployed token.
31 function run() public returns (Love) {44 function run() public returns (Love) {
32- bytes32 s = salt();
33 address w = wethAddress();45 address w = wethAddress();
34- address predicted = predict(s, w);46+ address predictedRegistry = registryAddress();
47+ address predicted = predict(salt());
35 48
36 console.log("deployer ", CREATE2_FACTORY);49 console.log("deployer ", CREATE2_FACTORY);
37- console.log("salt ", vm.toString(s));50+ console.log("registry ", predictedRegistry);
38 console.log("weth ", w);51 console.log("weth ", w);
39- console.log("initCodeHash ", vm.toString(initCodeHash(w)));52+ console.log("salt ", vm.toString(salt()));
53+ console.log("initCodeHash ", vm.toString(initCodeHash()));
40 console.log("predicted ", predicted);54 console.log("predicted ", predicted);
41 55
42- if (predicted.code.length > 0) {56+ _ensureRegistry(predictedRegistry);
43- console.log("already deployed, nothing to do");57+ _ensureRegistered(IWETH(w));
44- love = Love(predicted);58+ _ensureLove(predicted);
45- return love;
46- }
47-
48- vm.startBroadcast();
49- love = new Love{salt: s}(IERC20(w));
50- vm.stopBroadcast();
51-
52- require(address(love) == predicted, "LoveScript: address mismatch");
53-
54- console.log("Love deployed at", address(love));
55- console.log("name ", love.name());
56- console.log("symbol ", love.symbol());
57- console.log("weth ", address(love.WETH()));
58- console.log("rate ", love.RATE());
59- console.log("totalSupply ", love.totalSupply());
60 59
61 return love;60 return love;
62 }61 }
63 62
64- /// @notice Print the address `run()` would deploy to, without broadcasting.63+ /// @notice Print what `run()` would deploy, without broadcasting.
65- /// @return predicted The address the current salt and wETH derive to.64+ /// @return predicted The address the current salt derives to.
66 function predict() public view returns (address predicted) {65 function predict() public view returns (address predicted) {
67- bytes32 s = salt();66+ predicted = predict(salt());
68- address w = wethAddress();
69- predicted = predict(s, w);
70 67
71- console.log("salt ", vm.toString(s));68+ console.log("registry ", registryAddress());
72- console.log("weth ", w);69+ console.log("weth ", wethAddress());
73- console.log("initCodeHash ", vm.toString(initCodeHash(w)));70+ console.log("salt ", vm.toString(salt()));
71+ console.log("initCodeHash ", vm.toString(initCodeHash()));
74 console.log("predicted ", predicted);72 console.log("predicted ", predicted);
75 }73 }
76 74
77- /// @notice Derive the deployment address from a salt and a wETH address.75+ /// @notice Derive the token's deployment address from a salt.
76+ /// @dev Takes no wETH argument, unlike the address derivation this
77+ /// replaces: wETH is no longer in the creation code, which is why the
78+ /// answer is the same on every chain.
78 /// @param s The CREATE2 salt.79 /// @param s The CREATE2 salt.
79- /// @param w The wETH the token would be pegged to.
80 /// @return The address `Love` would land at.80 /// @return The address `Love` would land at.
81- function predict(bytes32 s, address w) public pure returns (address) {81+ function predict(bytes32 s) public pure returns (address) {
82- return vm.computeCreate2Address(s, initCodeHash(w), CREATE2_FACTORY);82+ return vm.computeCreate2Address(s, initCodeHash(), CREATE2_FACTORY);
83+ }
84+
85+ /// @notice The address the registry lands at on every chain.
86+ /// @return The registry's CREATE2 address.
87+ function registryAddress() public pure returns (address) {
88+ return vm.computeCreate2Address(REGISTRY_SALT, keccak256(registryInitCode()), CREATE2_FACTORY);
83 }89 }
84 90
85- /// @notice The creation code CREATE2 is handed, constructor argument included.91+ /// @notice The token's creation code, which now carries no arguments.
86- /// @param w The wETH the token would be pegged to.92+ /// @return The creation code handed to CREATE2.
87- /// @return The creation code, with `w` ABI-encoded onto it.93+ function initCode() public pure returns (bytes memory) {
88- function initCode(address w) public pure returns (bytes memory) {94+ return type(Love).creationCode;
89- return abi.encodePacked(type(Love).creationCode, abi.encode(w));
90 }95 }
91 96
92- /// @notice Hash of the creation code, the second input to the address derivation.97+ /// @notice Hash of the token's creation code.
93- /// @param w The wETH the token would be pegged to.98+ /// @return The keccak256 of `initCode()`.
94- /// @return The keccak256 of `initCode(w)`.99+ function initCodeHash() public pure returns (bytes32) {
95- function initCodeHash(address w) public pure returns (bytes32) {100+ return keccak256(initCode());
96- return keccak256(initCode(w));
97 }101 }
98 102
99- /// @notice The salt to deploy with.103+ /// @notice The registry's creation code.
100- /// @dev `SALT` overrides the default, e.g. to mine a vanity address.104+ /// @return The creation code handed to CREATE2.
105+ function registryInitCode() public pure returns (bytes memory) {
106+ return type(WETHRegistry).creationCode;
107+ }
108+
109+ /// @notice The salt to deploy the token with.
110+ /// @dev `SALT` overrides the default, e.g. to mine a vanity address or to
111+ /// route around a chain where the registry has been squatted.
101 /// @return The configured salt, or `DEFAULT_SALT`.112 /// @return The configured salt, or `DEFAULT_SALT`.
102 function salt() public view returns (bytes32) {113 function salt() public view returns (bytes32) {
103 return vm.envOr("SALT", DEFAULT_SALT);114 return vm.envOr("SALT", DEFAULT_SALT);
104 }115 }
105 116
106- /// @notice The wETH address to peg the deployment to.117+ /// @notice The wETH address to register on this chain.
107- /// @dev `WETH` overrides the default, which only holds on OP-Stack chains.118+ /// @dev `WETH` overrides the default, which only holds on OP Stack chains.
119+ /// Ignored once a wETH is already registered.
108 /// @return The configured wETH address, or `DEFAULT_WETH`.120 /// @return The configured wETH address, or `DEFAULT_WETH`.
109 function wethAddress() public view returns (address) {121 function wethAddress() public view returns (address) {
110 return vm.envOr("WETH", DEFAULT_WETH);122 return vm.envOr("WETH", DEFAULT_WETH);
111 }123 }
124+
125+ /// @dev Deploys the registry unless it is already there.
126+ /// @param predicted Where it should land.
127+ function _ensureRegistry(address predicted) private {
128+ if (predicted.code.length > 0) {
129+ console.log("registry already deployed");
130+ registry = WETHRegistry(payable(predicted));
131+ return;
132+ }
133+
134+ vm.startBroadcast();
135+ registry = new WETHRegistry{salt: REGISTRY_SALT}();
136+ vm.stopBroadcast();
137+
138+ require(address(registry) == predicted, "LoveScript: registry address mismatch");
139+ console.log("registry deployed at", address(registry));
140+ }
141+
142+ /// @dev Registers `candidate` unless a wETH is already registered. Reverts
143+ /// inside the registry if the candidate's code is not a reviewed wETH
144+ /// — which is the intended outcome on a chain whose wETH has not been
145+ /// reviewed, not a script failure to work around.
146+ /// @param candidate The wETH to register.
147+ function _ensureRegistered(IWETH candidate) private {
148+ IWETH registered = registry.weth();
149+
150+ if (address(registered) != address(0)) {
151+ console.log("weth already registered", address(registered));
152+ require(
153+ address(registered) == address(candidate) || address(candidate) == DEFAULT_WETH,
154+ "LoveScript: a different weth is registered on this chain"
155+ );
156+ return;
157+ }
158+
159+ vm.startBroadcast();
160+ registry.register{value: registry.PROBE()}(candidate);
161+ vm.stopBroadcast();
162+
163+ console.log("weth registered", address(registry.weth()));
164+ }
165+
166+ /// @dev Deploys the token unless it is already there.
167+ /// @param predicted Where it should land.
168+ function _ensureLove(address predicted) private {
169+ if (predicted.code.length > 0) {
170+ console.log("already deployed, nothing to do");
171+ love = Love(predicted);
172+ return;
173+ }
174+
175+ vm.startBroadcast();
176+ love = new Love{salt: salt()}();
177+ vm.stopBroadcast();
178+
179+ require(address(love) == predicted, "LoveScript: address mismatch");
180+
181+ console.log("Love deployed at", address(love));
182+ console.log("name ", love.name());
183+ console.log("symbol ", love.symbol());
184+ console.log("weth ", address(love.WETH()));
185+ console.log("rate ", love.RATE());
186+ console.log("totalSupply ", love.totalSupply());
187+ }
112 }188 }
modified src/Love.sol +42 -5
@@ -1,6 +1,8 @@
11 // SPDX-License-Identifier: MIT
22 pragma solidity ^0.8.30;
33
4+import {IWETH} from "./IWETH.sol";
5+import {WETHRegistry} from "./WETHRegistry.sol";
46 import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
57 import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
68 import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
@@ -21,9 +23,23 @@ contract Love is ERC20 {
2123 /// @notice LOVE minted per unit of wETH, and burned per unit released.
2224 uint256 public constant RATE = 100_000;
2325
26+ /// @notice The registry this token asks which wETH to peg to.
27+ /// @dev `WETHRegistry` takes no constructor arguments, so CREATE2 puts it
28+ /// at this address on every chain. Asking it at construction time,
29+ /// rather than taking wETH as a constructor argument, is what keeps
30+ /// this contract's creation code byte-identical everywhere — and so
31+ /// what gives LOVE one address on every chain instead of one per wETH
32+ /// deployment.
33+ ///
34+ /// Derived from the registry's creation code, which means it moves if
35+ /// `WETHRegistry`, the solc version or the optimizer settings change.
36+ /// `LoveRegistryAddressTest` recomputes it and fails if this constant
37+ /// has drifted.
38+ address public constant REGISTRY = 0x9Cf17430fEdEC487518416D1Cdc849b1eE9CDbA3;
39+
2440 /// @notice The wETH this token is pegged to and collateralised with.
25- /// @dev Immutable, and part of the creation code — two chains only give this
26- /// contract the same CREATE2 address if they share a wETH address.
41+ /// @dev Read once from the registry and immutable thereafter. The registry
42+ /// is itself write-once, so nothing can move the token under the peg.
2743 IERC20 public immutable WETH;
2844
2945 /// @notice Thrown when a withdrawal amount is not a multiple of `RATE`.
@@ -31,6 +47,14 @@ contract Love is ERC20 {
3147 /// @param rate The rate it has to be a multiple of.
3248 error AmountNotDivisibleByRate(uint256 loveAmount, uint256 rate);
3349
50+ /// @notice Thrown when there is no registry on this chain yet.
51+ /// @param registry The address the registry would be at.
52+ error RegistryNotDeployed(address registry);
53+
54+ /// @notice Thrown when the registry exists but holds no wETH yet.
55+ /// @param registry The registry that was asked.
56+ error WethNotRegistered(address registry);
57+
3458 /// @notice Emitted when wETH is locked and LOVE minted.
3559 /// @param account The depositor, who pays the wETH and receives the LOVE.
3660 /// @param wethAmount The wETH pulled in.
@@ -43,9 +67,22 @@ contract Love is ERC20 {
4367 /// @param wethAmount The wETH released, `loveAmount / RATE`.
4468 event Withdraw(address indexed account, uint256 loveAmount, uint256 wethAmount);
4569
46- /// @param weth_ The wETH to peg to. Set once, never changed.
47- constructor(IERC20 weth_) ERC20("Love", "LOVE") {
48- WETH = weth_;
70+ /// @notice Peg to whatever wETH the registry has accepted on this chain.
71+ /// @dev Takes no arguments on purpose. Anything passed in here would land
72+ /// in the creation code and give the token a different address on
73+ /// every chain whose wETH sits elsewhere, which is exactly what this
74+ /// design exists to avoid.
75+ ///
76+ /// Reverts when the registry is missing or empty, so a chain with no
77+ /// reviewed wETH gets no half-configured token: deploy the registry
78+ /// and register wETH first, then deploy this.
79+ constructor() ERC20("Love", "LOVE") {
80+ if (REGISTRY.code.length == 0) revert RegistryNotDeployed(REGISTRY);
81+
82+ IWETH registered = WETHRegistry(payable(REGISTRY)).weth();
83+ if (address(registered) == address(0)) revert WethNotRegistered(REGISTRY);
84+
85+ WETH = IERC20(address(registered));
4986 }
5087
5188 /// @notice Lock `wethAmount` wETH and mint `wethAmount * RATE` LOVE to the caller.
@@ -1,6 +1,8 @@
1 // SPDX-License-Identifier: MIT1 // SPDX-License-Identifier: MIT
2 pragma solidity ^0.8.30;2 pragma solidity ^0.8.30;
3 3
4+import {IWETH} from "./IWETH.sol";
5+import {WETHRegistry} from "./WETHRegistry.sol";
4 import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";6 import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
5 import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";7 import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
6 import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";8 import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
@@ -21,9 +23,23 @@ contract Love is ERC20 {
21 /// @notice LOVE minted per unit of wETH, and burned per unit released.23 /// @notice LOVE minted per unit of wETH, and burned per unit released.
22 uint256 public constant RATE = 100_000;24 uint256 public constant RATE = 100_000;
23 25
26+ /// @notice The registry this token asks which wETH to peg to.
27+ /// @dev `WETHRegistry` takes no constructor arguments, so CREATE2 puts it
28+ /// at this address on every chain. Asking it at construction time,
29+ /// rather than taking wETH as a constructor argument, is what keeps
30+ /// this contract's creation code byte-identical everywhere — and so
31+ /// what gives LOVE one address on every chain instead of one per wETH
32+ /// deployment.
33+ ///
34+ /// Derived from the registry's creation code, which means it moves if
35+ /// `WETHRegistry`, the solc version or the optimizer settings change.
36+ /// `LoveRegistryAddressTest` recomputes it and fails if this constant
37+ /// has drifted.
38+ address public constant REGISTRY = 0x9Cf17430fEdEC487518416D1Cdc849b1eE9CDbA3;
39+
24 /// @notice The wETH this token is pegged to and collateralised with.40 /// @notice The wETH this token is pegged to and collateralised with.
25- /// @dev Immutable, and part of the creation code — two chains only give this41+ /// @dev Read once from the registry and immutable thereafter. The registry
26- /// contract the same CREATE2 address if they share a wETH address.42+ /// is itself write-once, so nothing can move the token under the peg.
27 IERC20 public immutable WETH;43 IERC20 public immutable WETH;
28 44
29 /// @notice Thrown when a withdrawal amount is not a multiple of `RATE`.45 /// @notice Thrown when a withdrawal amount is not a multiple of `RATE`.
@@ -31,6 +47,14 @@ contract Love is ERC20 {
31 /// @param rate The rate it has to be a multiple of.47 /// @param rate The rate it has to be a multiple of.
32 error AmountNotDivisibleByRate(uint256 loveAmount, uint256 rate);48 error AmountNotDivisibleByRate(uint256 loveAmount, uint256 rate);
33 49
50+ /// @notice Thrown when there is no registry on this chain yet.
51+ /// @param registry The address the registry would be at.
52+ error RegistryNotDeployed(address registry);
53+
54+ /// @notice Thrown when the registry exists but holds no wETH yet.
55+ /// @param registry The registry that was asked.
56+ error WethNotRegistered(address registry);
57+
34 /// @notice Emitted when wETH is locked and LOVE minted.58 /// @notice Emitted when wETH is locked and LOVE minted.
35 /// @param account The depositor, who pays the wETH and receives the LOVE.59 /// @param account The depositor, who pays the wETH and receives the LOVE.
36 /// @param wethAmount The wETH pulled in.60 /// @param wethAmount The wETH pulled in.
@@ -43,9 +67,22 @@ contract Love is ERC20 {
43 /// @param wethAmount The wETH released, `loveAmount / RATE`.67 /// @param wethAmount The wETH released, `loveAmount / RATE`.
44 event Withdraw(address indexed account, uint256 loveAmount, uint256 wethAmount);68 event Withdraw(address indexed account, uint256 loveAmount, uint256 wethAmount);
45 69
46- /// @param weth_ The wETH to peg to. Set once, never changed.70+ /// @notice Peg to whatever wETH the registry has accepted on this chain.
47- constructor(IERC20 weth_) ERC20("Love", "LOVE") {71+ /// @dev Takes no arguments on purpose. Anything passed in here would land
48- WETH = weth_;72+ /// in the creation code and give the token a different address on
73+ /// every chain whose wETH sits elsewhere, which is exactly what this
74+ /// design exists to avoid.
75+ ///
76+ /// Reverts when the registry is missing or empty, so a chain with no
77+ /// reviewed wETH gets no half-configured token: deploy the registry
78+ /// and register wETH first, then deploy this.
79+ constructor() ERC20("Love", "LOVE") {
80+ if (REGISTRY.code.length == 0) revert RegistryNotDeployed(REGISTRY);
81+
82+ IWETH registered = WETHRegistry(payable(REGISTRY)).weth();
83+ if (address(registered) == address(0)) revert WethNotRegistered(REGISTRY);
84+
85+ WETH = IERC20(address(registered));
49 }86 }
50 87
51 /// @notice Lock `wethAmount` wETH and mint `wethAmount * RATE` LOVE to the caller.88 /// @notice Lock `wethAmount` wETH and mint `wethAmount * RATE` LOVE to the caller.
modified src/WETHRegistry.sol +8 -9
@@ -49,6 +49,7 @@ contract WETHRegistry {
4949 IWETH public weth;
5050
5151 /// @notice The ether moved through the candidate to prove it wraps.
52+ /// @dev Stays in the registry afterwards; see `register`.
5253 uint256 public constant PROBE = 1 wei;
5354
5455 /// @dev Open only for the duration of the round trip, so the registry
@@ -87,9 +88,6 @@ contract WETHRegistry {
8788 /// @notice Thrown when ether is sent outside a round trip.
8889 error NotProbing();
8990
90- /// @notice Thrown when the probe wei could not be sent back.
91- error RefundFailed();
92-
9391 /// @notice Emitted once, when a chain's wETH is settled.
9492 /// @param weth The accepted wETH.
9593 /// @param registrar Whoever supplied and paid for it.
@@ -98,7 +96,13 @@ contract WETHRegistry {
9896
9997 /// @notice Accept `candidate` as this chain's wETH, if its code is one of
10098 /// the reviewed implementations and it wraps ether correctly.
101- /// @dev Send exactly `PROBE` wei; it makes the round trip and comes back.
99+ /// @dev Send exactly `PROBE` wei. It is not refunded: it stays here, which
100+ /// is the cheap way to let a contract register. Paying it back would
101+ /// mean calling the registrar with value, and a registrar with no
102+ /// payable fallback — a script, a multisig, a deployer contract —
103+ /// would then be unable to register at all. One wei, once per chain,
104+ /// buys that away.
105+ ///
102106 /// Reverts rather than degrading when the candidate is unrecognised —
103107 /// unreviewed bytecode cannot be shown safe by any test, static or
104108 /// behavioural, so a chain running its own wETH is a chain `Love`
@@ -118,11 +122,6 @@ contract WETHRegistry {
118122 weth = candidate;
119123 emit Registered(candidate, msg.sender, codeHash);
120124
121- // After the state is settled, so a registrar that re-enters here finds
122- // the registry already closed.
123- (bool ok,) = msg.sender.call{value: PROBE}("");
124- if (!ok) revert RefundFailed();
125-
126125 return candidate;
127126 }
128127
@@ -49,6 +49,7 @@ contract WETHRegistry {
49 IWETH public weth;49 IWETH public weth;
50 50
51 /// @notice The ether moved through the candidate to prove it wraps.51 /// @notice The ether moved through the candidate to prove it wraps.
52+ /// @dev Stays in the registry afterwards; see `register`.
52 uint256 public constant PROBE = 1 wei;53 uint256 public constant PROBE = 1 wei;
53 54
54 /// @dev Open only for the duration of the round trip, so the registry55 /// @dev Open only for the duration of the round trip, so the registry
@@ -87,9 +88,6 @@ contract WETHRegistry {
87 /// @notice Thrown when ether is sent outside a round trip.88 /// @notice Thrown when ether is sent outside a round trip.
88 error NotProbing();89 error NotProbing();
89 90
90- /// @notice Thrown when the probe wei could not be sent back.
91- error RefundFailed();
92-
93 /// @notice Emitted once, when a chain's wETH is settled.91 /// @notice Emitted once, when a chain's wETH is settled.
94 /// @param weth The accepted wETH.92 /// @param weth The accepted wETH.
95 /// @param registrar Whoever supplied and paid for it.93 /// @param registrar Whoever supplied and paid for it.
@@ -98,7 +96,13 @@ contract WETHRegistry {
98 96
99 /// @notice Accept `candidate` as this chain's wETH, if its code is one of97 /// @notice Accept `candidate` as this chain's wETH, if its code is one of
100 /// the reviewed implementations and it wraps ether correctly.98 /// the reviewed implementations and it wraps ether correctly.
101- /// @dev Send exactly `PROBE` wei; it makes the round trip and comes back.99+ /// @dev Send exactly `PROBE` wei. It is not refunded: it stays here, which
100+ /// is the cheap way to let a contract register. Paying it back would
101+ /// mean calling the registrar with value, and a registrar with no
102+ /// payable fallback — a script, a multisig, a deployer contract —
103+ /// would then be unable to register at all. One wei, once per chain,
104+ /// buys that away.
105+ ///
102 /// Reverts rather than degrading when the candidate is unrecognised —106 /// Reverts rather than degrading when the candidate is unrecognised —
103 /// unreviewed bytecode cannot be shown safe by any test, static or107 /// unreviewed bytecode cannot be shown safe by any test, static or
104 /// behavioural, so a chain running its own wETH is a chain `Love`108 /// behavioural, so a chain running its own wETH is a chain `Love`
@@ -118,11 +122,6 @@ contract WETHRegistry {
118 weth = candidate;122 weth = candidate;
119 emit Registered(candidate, msg.sender, codeHash);123 emit Registered(candidate, msg.sender, codeHash);
120 124
121- // After the state is settled, so a registrar that re-enters here finds
122- // the registry already closed.
123- (bool ok,) = msg.sender.call{value: PROBE}("");
124- if (!ok) revert RefundFailed();
125-
126 return candidate;125 return candidate;
127 }126 }
128 127
added test/Fixtures.sol +78 -0
new file mode 100644
@@ -0,0 +1,78 @@
1+// SPDX-License-Identifier: MIT
2+pragma solidity ^0.8.30;
3+
4+import {IWETH} from "../src/IWETH.sol";
5+import {WETHRegistry} from "../src/WETHRegistry.sol";
6+import {CommonBase} from "forge-std/Base.sol";
7+import {StdCheats} from "forge-std/StdCheats.sol";
8+
9+/// @title Fixtures
10+/// @author Julien Béranger
11+/// @notice Brings a chain up the way a real one comes up: the registry at its
12+/// deterministic address, a genuine wETH at some address, registered.
13+/// @dev The wETH here is real runtime bytecode lifted off a live chain, etched
14+/// into place. A mock would need its codehash added to the allowlist to
15+/// be registrable, which would mean testing a different allowlist from
16+/// the one that ships. Etching real code keeps the test and the product
17+/// honest about the same ten hashes.
18+abstract contract Fixtures is CommonBase, StdCheats {
19+ /// @notice Salt the registry is deployed with, here and in the script.
20+ bytes32 internal constant REGISTRY_SALT = keccak256("LOVE.WETHRegistry");
21+
22+ /// @notice OP Stack legacy WETH9, as deployed on Base, Mode and Zora.
23+ string internal constant WETH9_OP_LEGACY = "test/fixtures/weth9-op-legacy.hex";
24+
25+ /// @notice Canonical WETH9, as deployed on Ethereum mainnet.
26+ string internal constant WETH9_CANONICAL = "test/fixtures/weth9-canonical.hex";
27+
28+ /// @notice Put real wETH runtime bytecode at `where`.
29+ /// @dev Reads the fixture rather than embedding it, so refreshing a
30+ /// fixture from chain state does not mean editing Solidity.
31+ /// @param where The address to place it at.
32+ /// @param fixture Path to the runtime bytecode, `WETH9_*` above.
33+ /// @return The wETH now living at `where`.
34+ function etchWeth(address where, string memory fixture) internal returns (IWETH) {
35+ vm.etch(where, vm.parseBytes(vm.trim(vm.readFile(fixture))));
36+ vm.label(where, "WETH");
37+ return IWETH(where);
38+ }
39+
40+ /// @notice Deploy the registry to the address `Love` expects.
41+ /// @dev Through the canonical deterministic deployer with `REGISTRY_SALT`,
42+ /// exactly as the deploy script does, so the address it lands at is
43+ /// the one compiled into `Love.REGISTRY` rather than one arranged for
44+ /// the test.
45+ /// @return The registry.
46+ function deployRegistry() internal returns (WETHRegistry) {
47+ (bool ok, bytes memory ret) =
48+ CREATE2_FACTORY.call(abi.encodePacked(REGISTRY_SALT, type(WETHRegistry).creationCode));
49+ require(ok, "registry deployment failed");
50+
51+ // casting to 'bytes20' is safe because the deployer returns the 20-byte address, raw
52+ // forge-lint: disable-next-line(unsafe-typecast)
53+ WETHRegistry registry = WETHRegistry(payable(address(bytes20(ret))));
54+ vm.label(address(registry), "WETHRegistry");
55+
56+ return registry;
57+ }
58+
59+ /// @notice Register `weth`, funding the one-wei probe.
60+ /// @param registry The registry to register with.
61+ /// @param weth The wETH to register.
62+ function register(WETHRegistry registry, IWETH weth) internal {
63+ uint256 probe = registry.PROBE();
64+ deal(address(this), address(this).balance + probe);
65+ registry.register{value: probe}(weth);
66+ }
67+
68+ /// @notice The whole chain in one call: registry, wETH, registration.
69+ /// @param wethAt The address to etch wETH at.
70+ /// @param fixture Which implementation to etch.
71+ /// @return registry The registry, with `wethAt` registered.
72+ /// @return weth The wETH now backing any `Love` deployed here.
73+ function setUpChain(address wethAt, string memory fixture) internal returns (WETHRegistry registry, IWETH weth) {
74+ registry = deployRegistry();
75+ weth = etchWeth(wethAt, fixture);
76+ register(registry, weth);
77+ }
78+}
new file mode 100644
@@ -0,0 +1,78 @@
1+// SPDX-License-Identifier: MIT
2+pragma solidity ^0.8.30;
3+
4+import {IWETH} from "../src/IWETH.sol";
5+import {WETHRegistry} from "../src/WETHRegistry.sol";
6+import {CommonBase} from "forge-std/Base.sol";
7+import {StdCheats} from "forge-std/StdCheats.sol";
8+
9+/// @title Fixtures
10+/// @author Julien Béranger
11+/// @notice Brings a chain up the way a real one comes up: the registry at its
12+/// deterministic address, a genuine wETH at some address, registered.
13+/// @dev The wETH here is real runtime bytecode lifted off a live chain, etched
14+/// into place. A mock would need its codehash added to the allowlist to
15+/// be registrable, which would mean testing a different allowlist from
16+/// the one that ships. Etching real code keeps the test and the product
17+/// honest about the same ten hashes.
18+abstract contract Fixtures is CommonBase, StdCheats {
19+ /// @notice Salt the registry is deployed with, here and in the script.
20+ bytes32 internal constant REGISTRY_SALT = keccak256("LOVE.WETHRegistry");
21+
22+ /// @notice OP Stack legacy WETH9, as deployed on Base, Mode and Zora.
23+ string internal constant WETH9_OP_LEGACY = "test/fixtures/weth9-op-legacy.hex";
24+
25+ /// @notice Canonical WETH9, as deployed on Ethereum mainnet.
26+ string internal constant WETH9_CANONICAL = "test/fixtures/weth9-canonical.hex";
27+
28+ /// @notice Put real wETH runtime bytecode at `where`.
29+ /// @dev Reads the fixture rather than embedding it, so refreshing a
30+ /// fixture from chain state does not mean editing Solidity.
31+ /// @param where The address to place it at.
32+ /// @param fixture Path to the runtime bytecode, `WETH9_*` above.
33+ /// @return The wETH now living at `where`.
34+ function etchWeth(address where, string memory fixture) internal returns (IWETH) {
35+ vm.etch(where, vm.parseBytes(vm.trim(vm.readFile(fixture))));
36+ vm.label(where, "WETH");
37+ return IWETH(where);
38+ }
39+
40+ /// @notice Deploy the registry to the address `Love` expects.
41+ /// @dev Through the canonical deterministic deployer with `REGISTRY_SALT`,
42+ /// exactly as the deploy script does, so the address it lands at is
43+ /// the one compiled into `Love.REGISTRY` rather than one arranged for
44+ /// the test.
45+ /// @return The registry.
46+ function deployRegistry() internal returns (WETHRegistry) {
47+ (bool ok, bytes memory ret) =
48+ CREATE2_FACTORY.call(abi.encodePacked(REGISTRY_SALT, type(WETHRegistry).creationCode));
49+ require(ok, "registry deployment failed");
50+
51+ // casting to 'bytes20' is safe because the deployer returns the 20-byte address, raw
52+ // forge-lint: disable-next-line(unsafe-typecast)
53+ WETHRegistry registry = WETHRegistry(payable(address(bytes20(ret))));
54+ vm.label(address(registry), "WETHRegistry");
55+
56+ return registry;
57+ }
58+
59+ /// @notice Register `weth`, funding the one-wei probe.
60+ /// @param registry The registry to register with.
61+ /// @param weth The wETH to register.
62+ function register(WETHRegistry registry, IWETH weth) internal {
63+ uint256 probe = registry.PROBE();
64+ deal(address(this), address(this).balance + probe);
65+ registry.register{value: probe}(weth);
66+ }
67+
68+ /// @notice The whole chain in one call: registry, wETH, registration.
69+ /// @param wethAt The address to etch wETH at.
70+ /// @param fixture Which implementation to etch.
71+ /// @return registry The registry, with `wethAt` registered.
72+ /// @return weth The wETH now backing any `Love` deployed here.
73+ function setUpChain(address wethAt, string memory fixture) internal returns (WETHRegistry registry, IWETH weth) {
74+ registry = deployRegistry();
75+ weth = etchWeth(wethAt, fixture);
76+ register(registry, weth);
77+ }
78+}
modified test/Love.t.sol +18 -13
@@ -1,22 +1,23 @@
11 // SPDX-License-Identifier: MIT
22 pragma solidity ^0.8.30;
33
4+import {IWETH} from "../src/IWETH.sol";
45 import {Love} from "../src/Love.sol";
5-import {MockWETH} from "./mocks/MockWETH.sol";
6+import {Fixtures} from "./Fixtures.sol";
67 import {IERC20Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol";
78 import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
89 import {Test} from "forge-std/Test.sol";
910
1011 /// @title LoveTest
11-/// @notice Exercises the peg end to end against a WETH9 stand-in: minting on
12+/// @notice Exercises the peg end to end against real WETH9 bytecode: minting on
1213 /// deposit, redemption on withdraw, the divisibility rule, and the
1314 /// invariant that supply always equals the wETH backing times the rate.
14-contract LoveTest is Test {
15+contract LoveTest is Test, Fixtures {
1516 /// @notice The token under test.
1617 Love public love;
1718
18- /// @notice The wETH it is pegged to.
19- MockWETH public weth;
19+ /// @notice The wETH it is pegged to, etched from a live chain.
20+ IWETH public weth;
2021
2122 address alice = makeAddr("alice");
2223 address bob = makeAddr("bob");
@@ -24,9 +25,12 @@ contract LoveTest is Test {
2425
2526 uint256 constant RATE = 100_000;
2627
28+ /// @dev The OP Stack predeploy address, so the setup mirrors a real chain.
29+ address constant WETH_AT = 0x4200000000000000000000000000000000000006;
30+
2731 function setUp() public {
28- weth = new MockWETH();
29- love = new Love(IERC20(address(weth)));
32+ (, weth) = setUpChain(WETH_AT, WETH9_OP_LEGACY);
33+ love = new Love();
3034
3135 _fund(alice, 10 ether);
3236 _fund(bob, 10 ether);
@@ -115,11 +119,14 @@ contract LoveTest is Test {
115119 assertEq(love.totalSupply(), 0);
116120 }
117121
122+ /// @dev WETH9 predates custom errors and guards `transferFrom` with a bare
123+ /// `require`, so a shortfall comes back as a revert carrying no data
124+ /// at all rather than as a typed ERC-20 error. Asserting the empty
125+ /// data keeps that documented: anything integrating with LOVE has a
126+ /// reason to fail, but not a machine-readable one.
118127 function test_RevertWhen_DepositWithoutApproval() public {
119128 vm.prank(alice);
120- vm.expectRevert(
121- abi.encodeWithSelector(IERC20Errors.ERC20InsufficientAllowance.selector, address(love), 0, 1 ether)
122- );
129+ vm.expectRevert(bytes(""));
123130 love.deposit(1 ether);
124131 }
125132
@@ -128,9 +135,7 @@ contract LoveTest is Test {
128135 weth.approve(address(love), 100 ether);
129136
130137 vm.prank(alice);
131- vm.expectRevert(
132- abi.encodeWithSelector(IERC20Errors.ERC20InsufficientBalance.selector, alice, 10 ether, 100 ether)
133- );
138+ vm.expectRevert(bytes(""));
134139 love.deposit(100 ether);
135140 }
136141
@@ -1,22 +1,23 @@
1 // SPDX-License-Identifier: MIT1 // SPDX-License-Identifier: MIT
2 pragma solidity ^0.8.30;2 pragma solidity ^0.8.30;
3 3
4+import {IWETH} from "../src/IWETH.sol";
4 import {Love} from "../src/Love.sol";5 import {Love} from "../src/Love.sol";
5-import {MockWETH} from "./mocks/MockWETH.sol";6+import {Fixtures} from "./Fixtures.sol";
6 import {IERC20Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol";7 import {IERC20Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol";
7 import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";8 import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
8 import {Test} from "forge-std/Test.sol";9 import {Test} from "forge-std/Test.sol";
9 10
10 /// @title LoveTest11 /// @title LoveTest
11-/// @notice Exercises the peg end to end against a WETH9 stand-in: minting on12+/// @notice Exercises the peg end to end against real WETH9 bytecode: minting on
12 /// deposit, redemption on withdraw, the divisibility rule, and the13 /// deposit, redemption on withdraw, the divisibility rule, and the
13 /// invariant that supply always equals the wETH backing times the rate.14 /// invariant that supply always equals the wETH backing times the rate.
14-contract LoveTest is Test {15+contract LoveTest is Test, Fixtures {
15 /// @notice The token under test.16 /// @notice The token under test.
16 Love public love;17 Love public love;
17 18
18- /// @notice The wETH it is pegged to.19+ /// @notice The wETH it is pegged to, etched from a live chain.
19- MockWETH public weth;20+ IWETH public weth;
20 21
21 address alice = makeAddr("alice");22 address alice = makeAddr("alice");
22 address bob = makeAddr("bob");23 address bob = makeAddr("bob");
@@ -24,9 +25,12 @@ contract LoveTest is Test {
24 25
25 uint256 constant RATE = 100_000;26 uint256 constant RATE = 100_000;
26 27
28+ /// @dev The OP Stack predeploy address, so the setup mirrors a real chain.
29+ address constant WETH_AT = 0x4200000000000000000000000000000000000006;
30+
27 function setUp() public {31 function setUp() public {
28- weth = new MockWETH();32+ (, weth) = setUpChain(WETH_AT, WETH9_OP_LEGACY);
29- love = new Love(IERC20(address(weth)));33+ love = new Love();
30 34
31 _fund(alice, 10 ether);35 _fund(alice, 10 ether);
32 _fund(bob, 10 ether);36 _fund(bob, 10 ether);
@@ -115,11 +119,14 @@ contract LoveTest is Test {
115 assertEq(love.totalSupply(), 0);119 assertEq(love.totalSupply(), 0);
116 }120 }
117 121
122+ /// @dev WETH9 predates custom errors and guards `transferFrom` with a bare
123+ /// `require`, so a shortfall comes back as a revert carrying no data
124+ /// at all rather than as a typed ERC-20 error. Asserting the empty
125+ /// data keeps that documented: anything integrating with LOVE has a
126+ /// reason to fail, but not a machine-readable one.
118 function test_RevertWhen_DepositWithoutApproval() public {127 function test_RevertWhen_DepositWithoutApproval() public {
119 vm.prank(alice);128 vm.prank(alice);
120- vm.expectRevert(129+ vm.expectRevert(bytes(""));
121- abi.encodeWithSelector(IERC20Errors.ERC20InsufficientAllowance.selector, address(love), 0, 1 ether)
122- );
123 love.deposit(1 ether);130 love.deposit(1 ether);
124 }131 }
125 132
@@ -128,9 +135,7 @@ contract LoveTest is Test {
128 weth.approve(address(love), 100 ether);135 weth.approve(address(love), 100 ether);
129 136
130 vm.prank(alice);137 vm.prank(alice);
131- vm.expectRevert(138+ vm.expectRevert(bytes(""));
132- abi.encodeWithSelector(IERC20Errors.ERC20InsufficientBalance.selector, alice, 10 ether, 100 ether)
133- );
134 love.deposit(100 ether);139 love.deposit(100 ether);
135 }140 }
136 141
modified test/LoveCreate2.t.sol +110 -26
@@ -3,16 +3,20 @@ pragma solidity ^0.8.30;
33
44 import {LoveScript} from "../script/Love.s.sol";
55 import {Love} from "../src/Love.sol";
6+import {Fixtures} from "./Fixtures.sol";
67 import {Test} from "forge-std/Test.sol";
78
89 /// @title LoveCreate2Test
9-/// @notice The deployment address derives from the salt, the creation code and
10-/// the wETH constructor argument. These tests pin that derivation: same
11-/// salt and same wETH give the same address on any chain, a different
12-/// wETH gives a different one.
13-contract LoveCreate2Test is Test {
10+/// @notice The deployment address now derives from the salt and the creation
11+/// code alone. wETH used to be a constructor argument and so part of
12+/// that creation code, which gave the token a different address on
13+/// every chain whose wETH sat elsewhere; it is read from the registry
14+/// instead. These tests pin the consequence: same salt, same address,
15+/// on any chain, whatever its wETH is and wherever it lives.
16+contract LoveCreate2Test is Test, Fixtures {
1417 bytes32 constant SALT = keccak256("LOVE");
15- address constant WETH = 0x4200000000000000000000000000000000000006;
18+ address constant OP_STACK_WETH = 0x4200000000000000000000000000000000000006;
19+ address constant MAINNET_WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
1620
1721 LoveScript script;
1822
@@ -23,19 +27,56 @@ contract LoveCreate2Test is Test {
2327 assertGt(CREATE2_FACTORY.code.length, 0, "create2 deployer missing");
2428 }
2529
30+ /*//////////////////////////////////////////////////////////////
31+ THE REGISTRY
32+ //////////////////////////////////////////////////////////////*/
33+
34+ /// @dev `Love` has the registry's address compiled in, so if the registry's
35+ /// creation code moves — an edit to the contract, a solc bump, a
36+ /// change of optimizer settings — this fails rather than shipping a
37+ /// token pointing at an empty address.
38+ function test_LoveRegistryConstantMatchesDeterministicAddress() public {
39+ setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY);
40+
41+ assertEq(new Love().REGISTRY(), script.registryAddress());
42+ }
43+
44+ function test_RegistryLandsAtTheAddressLoveExpects() public {
45+ assertEq(address(deployRegistry()), script.registryAddress());
46+ }
47+
48+ /// @dev The registry takes no constructor arguments, which is what makes
49+ /// its address the same everywhere.
50+ function test_RegistryAddressIsChainAgnostic() public {
51+ address onThisChain = script.registryAddress();
52+
53+ vm.chainId(137);
54+ assertEq(script.registryAddress(), onThisChain);
55+
56+ vm.chainId(42_161);
57+ assertEq(script.registryAddress(), onThisChain);
58+ }
59+
60+ /*//////////////////////////////////////////////////////////////
61+ THE DERIVATION
62+ //////////////////////////////////////////////////////////////*/
63+
2664 function test_DeploysAtPredictedAddress() public {
27- address predicted = vm.computeCreate2Address(SALT, keccak256(script.initCode(WETH)), CREATE2_FACTORY);
65+ setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY);
2866
29- Love deployed = Love(_deploy(SALT, WETH));
67+ address predicted = vm.computeCreate2Address(SALT, keccak256(script.initCode()), CREATE2_FACTORY);
68+ Love deployed = Love(_deploy(SALT));
3069
3170 assertEq(address(deployed), predicted);
3271 assertEq(deployed.symbol(), "LOVE");
33- assertEq(address(deployed.WETH()), WETH);
72+ assertEq(address(deployed.WETH()), OP_STACK_WETH);
3473 }
3574
3675 /// @dev What the deploy script prints must be what the chain gives back.
3776 function test_ScriptPredictionMatchesDeployment() public {
38- assertEq(script.predict(SALT, WETH), _deploy(SALT, WETH));
77+ setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY);
78+
79+ assertEq(script.predict(SALT), _deploy(SALT));
3980 }
4081
4182 function test_ScriptUsesKeccakOfLoveAsDefaultSalt() public view {
@@ -43,46 +84,89 @@ contract LoveCreate2Test is Test {
4384 }
4485
4586 function test_ScriptDefaultsToOpStackWeth() public view {
46- assertEq(script.DEFAULT_WETH(), WETH);
87+ assertEq(script.DEFAULT_WETH(), OP_STACK_WETH);
4788 }
4889
49- /// @dev Same salt, same code, same wETH, same address.
90+ /// @dev Same salt, same code, same address — and unlike before, no wETH
91+ /// argument that could make the answer differ.
5092 function test_PredictionIsChainAgnostic() public {
51- address onThisChain = script.predict(SALT, WETH);
93+ address onThisChain = script.predict(SALT);
5294
5395 vm.chainId(137);
54- assertEq(script.predict(SALT, WETH), onThisChain);
96+ assertEq(script.predict(SALT), onThisChain);
5597
5698 vm.chainId(42_161);
57- assertEq(script.predict(SALT, WETH), onThisChain);
99+ assertEq(script.predict(SALT), onThisChain);
58100 }
59101
60102 function test_DifferentSaltsGiveDifferentAddresses() public view {
61- assertTrue(script.predict(SALT, WETH) != script.predict(keccak256("LOVE2"), WETH));
103+ assertTrue(script.predict(SALT) != script.predict(keccak256("LOVE2")));
62104 }
63105
64- /// @dev The wETH address is part of the creation code, so chains with their
65- /// own wETH get their own token address.
66- function test_DifferentWethGivesDifferentAddress() public view {
67- assertTrue(script.predict(SALT, WETH) != script.predict(SALT, address(0xBEEF)));
106+ /*//////////////////////////////////////////////////////////////
107+ THE POINT OF ALL THIS
108+ //////////////////////////////////////////////////////////////*/
109+
110+ /// @dev The test that replaces `test_DifferentWethGivesDifferentAddress`.
111+ /// Two chains, two different wETH implementations at two different
112+ /// addresses, one LOVE address. This is what the registry buys.
113+ function test_SameAddressOnChainsWithDifferentWeth() public {
114+ // A snapshot stands in for a second chain. Etching the code away is
115+ // not enough: a deployed account keeps its nonce, and CREATE2 refuses
116+ // to build over that, so the registry could never be redeployed.
117+ uint256 freshChain = vm.snapshotState();
118+
119+ setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY);
120+ address onOpStack = _deploy(SALT);
121+ assertEq(address(Love(onOpStack).WETH()), OP_STACK_WETH);
122+
123+ vm.revertToState(freshChain);
124+
125+ // Different chain id, a different wETH implementation, at a different
126+ // address.
127+ vm.chainId(1);
128+ setUpChain(MAINNET_WETH, WETH9_CANONICAL);
129+ address onMainnet = _deploy(SALT);
130+ assertEq(address(Love(onMainnet).WETH()), MAINNET_WETH);
131+
132+ assertEq(onOpStack, onMainnet, "LOVE must land at one address on both chains");
68133 }
69134
70135 /// @dev Redeploying with the same salt must fail, not silently return the
71136 /// existing token.
72137 function test_RevertWhen_RedeployingWithSameSalt() public {
73- _deploy(SALT, WETH);
138+ setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY);
139+ _deploy(SALT);
74140
75- (bool ok,) = CREATE2_FACTORY.call(abi.encodePacked(SALT, script.initCode(WETH)));
141+ (bool ok,) = CREATE2_FACTORY.call(abi.encodePacked(SALT, script.initCode()));
76142 assertFalse(ok);
77143 }
78144
79- function testFuzz_PredictionMatchesDeployment(bytes32 salt, address weth) public {
80- assertEq(script.predict(salt, weth), _deploy(salt, weth));
145+ function testFuzz_PredictionMatchesDeployment(bytes32 salt) public {
146+ setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY);
147+
148+ assertEq(script.predict(salt), _deploy(salt));
149+ }
150+
151+ /*//////////////////////////////////////////////////////////////
152+ CONSTRUCTOR GUARDS
153+ //////////////////////////////////////////////////////////////*/
154+
155+ function test_RevertWhen_RegistryNotDeployed() public {
156+ vm.expectRevert(abi.encodeWithSelector(Love.RegistryNotDeployed.selector, script.registryAddress()));
157+ new Love();
158+ }
159+
160+ function test_RevertWhen_WethNotRegistered() public {
161+ deployRegistry();
162+
163+ vm.expectRevert(abi.encodeWithSelector(Love.WethNotRegistered.selector, script.registryAddress()));
164+ new Love();
81165 }
82166
83167 /// @dev Deploys through the canonical deterministic deployer, as the script does.
84- function _deploy(bytes32 salt, address weth) internal returns (address deployed) {
85- (bool ok, bytes memory ret) = CREATE2_FACTORY.call(abi.encodePacked(salt, script.initCode(weth)));
168+ function _deploy(bytes32 salt) internal returns (address deployed) {
169+ (bool ok, bytes memory ret) = CREATE2_FACTORY.call(abi.encodePacked(salt, script.initCode()));
86170 require(ok, "create2 deployment failed");
87171 // casting to 'bytes20' is safe because the deployer returns the 20-byte address, raw
88172 // forge-lint: disable-next-line(unsafe-typecast)
@@ -3,16 +3,20 @@ pragma solidity ^0.8.30;
3 3
4 import {LoveScript} from "../script/Love.s.sol";4 import {LoveScript} from "../script/Love.s.sol";
5 import {Love} from "../src/Love.sol";5 import {Love} from "../src/Love.sol";
6+import {Fixtures} from "./Fixtures.sol";
6 import {Test} from "forge-std/Test.sol";7 import {Test} from "forge-std/Test.sol";
7 8
8 /// @title LoveCreate2Test9 /// @title LoveCreate2Test
9-/// @notice The deployment address derives from the salt, the creation code and10+/// @notice The deployment address now derives from the salt and the creation
10-/// the wETH constructor argument. These tests pin that derivation: same11+/// code alone. wETH used to be a constructor argument and so part of
11-/// salt and same wETH give the same address on any chain, a different12+/// that creation code, which gave the token a different address on
12-/// wETH gives a different one.13+/// every chain whose wETH sat elsewhere; it is read from the registry
13-contract LoveCreate2Test is Test {14+/// instead. These tests pin the consequence: same salt, same address,
15+/// on any chain, whatever its wETH is and wherever it lives.
16+contract LoveCreate2Test is Test, Fixtures {
14 bytes32 constant SALT = keccak256("LOVE");17 bytes32 constant SALT = keccak256("LOVE");
15- address constant WETH = 0x4200000000000000000000000000000000000006;18+ address constant OP_STACK_WETH = 0x4200000000000000000000000000000000000006;
19+ address constant MAINNET_WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
16 20
17 LoveScript script;21 LoveScript script;
18 22
@@ -23,19 +27,56 @@ contract LoveCreate2Test is Test {
23 assertGt(CREATE2_FACTORY.code.length, 0, "create2 deployer missing");27 assertGt(CREATE2_FACTORY.code.length, 0, "create2 deployer missing");
24 }28 }
25 29
30+ /*//////////////////////////////////////////////////////////////
31+ THE REGISTRY
32+ //////////////////////////////////////////////////////////////*/
33+
34+ /// @dev `Love` has the registry's address compiled in, so if the registry's
35+ /// creation code moves — an edit to the contract, a solc bump, a
36+ /// change of optimizer settings — this fails rather than shipping a
37+ /// token pointing at an empty address.
38+ function test_LoveRegistryConstantMatchesDeterministicAddress() public {
39+ setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY);
40+
41+ assertEq(new Love().REGISTRY(), script.registryAddress());
42+ }
43+
44+ function test_RegistryLandsAtTheAddressLoveExpects() public {
45+ assertEq(address(deployRegistry()), script.registryAddress());
46+ }
47+
48+ /// @dev The registry takes no constructor arguments, which is what makes
49+ /// its address the same everywhere.
50+ function test_RegistryAddressIsChainAgnostic() public {
51+ address onThisChain = script.registryAddress();
52+
53+ vm.chainId(137);
54+ assertEq(script.registryAddress(), onThisChain);
55+
56+ vm.chainId(42_161);
57+ assertEq(script.registryAddress(), onThisChain);
58+ }
59+
60+ /*//////////////////////////////////////////////////////////////
61+ THE DERIVATION
62+ //////////////////////////////////////////////////////////////*/
63+
26 function test_DeploysAtPredictedAddress() public {64 function test_DeploysAtPredictedAddress() public {
27- address predicted = vm.computeCreate2Address(SALT, keccak256(script.initCode(WETH)), CREATE2_FACTORY);65+ setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY);
28 66
29- Love deployed = Love(_deploy(SALT, WETH));67+ address predicted = vm.computeCreate2Address(SALT, keccak256(script.initCode()), CREATE2_FACTORY);
68+ Love deployed = Love(_deploy(SALT));
30 69
31 assertEq(address(deployed), predicted);70 assertEq(address(deployed), predicted);
32 assertEq(deployed.symbol(), "LOVE");71 assertEq(deployed.symbol(), "LOVE");
33- assertEq(address(deployed.WETH()), WETH);72+ assertEq(address(deployed.WETH()), OP_STACK_WETH);
34 }73 }
35 74
36 /// @dev What the deploy script prints must be what the chain gives back.75 /// @dev What the deploy script prints must be what the chain gives back.
37 function test_ScriptPredictionMatchesDeployment() public {76 function test_ScriptPredictionMatchesDeployment() public {
38- assertEq(script.predict(SALT, WETH), _deploy(SALT, WETH));77+ setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY);
78+
79+ assertEq(script.predict(SALT), _deploy(SALT));
39 }80 }
40 81
41 function test_ScriptUsesKeccakOfLoveAsDefaultSalt() public view {82 function test_ScriptUsesKeccakOfLoveAsDefaultSalt() public view {
@@ -43,46 +84,89 @@ contract LoveCreate2Test is Test {
43 }84 }
44 85
45 function test_ScriptDefaultsToOpStackWeth() public view {86 function test_ScriptDefaultsToOpStackWeth() public view {
46- assertEq(script.DEFAULT_WETH(), WETH);87+ assertEq(script.DEFAULT_WETH(), OP_STACK_WETH);
47 }88 }
48 89
49- /// @dev Same salt, same code, same wETH, same address.90+ /// @dev Same salt, same code, same address — and unlike before, no wETH
91+ /// argument that could make the answer differ.
50 function test_PredictionIsChainAgnostic() public {92 function test_PredictionIsChainAgnostic() public {
51- address onThisChain = script.predict(SALT, WETH);93+ address onThisChain = script.predict(SALT);
52 94
53 vm.chainId(137);95 vm.chainId(137);
54- assertEq(script.predict(SALT, WETH), onThisChain);96+ assertEq(script.predict(SALT), onThisChain);
55 97
56 vm.chainId(42_161);98 vm.chainId(42_161);
57- assertEq(script.predict(SALT, WETH), onThisChain);99+ assertEq(script.predict(SALT), onThisChain);
58 }100 }
59 101
60 function test_DifferentSaltsGiveDifferentAddresses() public view {102 function test_DifferentSaltsGiveDifferentAddresses() public view {
61- assertTrue(script.predict(SALT, WETH) != script.predict(keccak256("LOVE2"), WETH));103+ assertTrue(script.predict(SALT) != script.predict(keccak256("LOVE2")));
62 }104 }
63 105
64- /// @dev The wETH address is part of the creation code, so chains with their106+ /*//////////////////////////////////////////////////////////////
65- /// own wETH get their own token address.107+ THE POINT OF ALL THIS
66- function test_DifferentWethGivesDifferentAddress() public view {108+ //////////////////////////////////////////////////////////////*/
67- assertTrue(script.predict(SALT, WETH) != script.predict(SALT, address(0xBEEF)));109+
110+ /// @dev The test that replaces `test_DifferentWethGivesDifferentAddress`.
111+ /// Two chains, two different wETH implementations at two different
112+ /// addresses, one LOVE address. This is what the registry buys.
113+ function test_SameAddressOnChainsWithDifferentWeth() public {
114+ // A snapshot stands in for a second chain. Etching the code away is
115+ // not enough: a deployed account keeps its nonce, and CREATE2 refuses
116+ // to build over that, so the registry could never be redeployed.
117+ uint256 freshChain = vm.snapshotState();
118+
119+ setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY);
120+ address onOpStack = _deploy(SALT);
121+ assertEq(address(Love(onOpStack).WETH()), OP_STACK_WETH);
122+
123+ vm.revertToState(freshChain);
124+
125+ // Different chain id, a different wETH implementation, at a different
126+ // address.
127+ vm.chainId(1);
128+ setUpChain(MAINNET_WETH, WETH9_CANONICAL);
129+ address onMainnet = _deploy(SALT);
130+ assertEq(address(Love(onMainnet).WETH()), MAINNET_WETH);
131+
132+ assertEq(onOpStack, onMainnet, "LOVE must land at one address on both chains");
68 }133 }
69 134
70 /// @dev Redeploying with the same salt must fail, not silently return the135 /// @dev Redeploying with the same salt must fail, not silently return the
71 /// existing token.136 /// existing token.
72 function test_RevertWhen_RedeployingWithSameSalt() public {137 function test_RevertWhen_RedeployingWithSameSalt() public {
73- _deploy(SALT, WETH);138+ setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY);
139+ _deploy(SALT);
74 140
75- (bool ok,) = CREATE2_FACTORY.call(abi.encodePacked(SALT, script.initCode(WETH)));141+ (bool ok,) = CREATE2_FACTORY.call(abi.encodePacked(SALT, script.initCode()));
76 assertFalse(ok);142 assertFalse(ok);
77 }143 }
78 144
79- function testFuzz_PredictionMatchesDeployment(bytes32 salt, address weth) public {145+ function testFuzz_PredictionMatchesDeployment(bytes32 salt) public {
80- assertEq(script.predict(salt, weth), _deploy(salt, weth));146+ setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY);
147+
148+ assertEq(script.predict(salt), _deploy(salt));
149+ }
150+
151+ /*//////////////////////////////////////////////////////////////
152+ CONSTRUCTOR GUARDS
153+ //////////////////////////////////////////////////////////////*/
154+
155+ function test_RevertWhen_RegistryNotDeployed() public {
156+ vm.expectRevert(abi.encodeWithSelector(Love.RegistryNotDeployed.selector, script.registryAddress()));
157+ new Love();
158+ }
159+
160+ function test_RevertWhen_WethNotRegistered() public {
161+ deployRegistry();
162+
163+ vm.expectRevert(abi.encodeWithSelector(Love.WethNotRegistered.selector, script.registryAddress()));
164+ new Love();
81 }165 }
82 166
83 /// @dev Deploys through the canonical deterministic deployer, as the script does.167 /// @dev Deploys through the canonical deterministic deployer, as the script does.
84- function _deploy(bytes32 salt, address weth) internal returns (address deployed) {168+ function _deploy(bytes32 salt) internal returns (address deployed) {
85- (bool ok, bytes memory ret) = CREATE2_FACTORY.call(abi.encodePacked(salt, script.initCode(weth)));169+ (bool ok, bytes memory ret) = CREATE2_FACTORY.call(abi.encodePacked(salt, script.initCode()));
86 require(ok, "create2 deployment failed");170 require(ok, "create2 deployment failed");
87 // casting to 'bytes20' is safe because the deployer returns the 20-byte address, raw171 // casting to 'bytes20' is safe because the deployer returns the 20-byte address, raw
88 // forge-lint: disable-next-line(unsafe-typecast)172 // forge-lint: disable-next-line(unsafe-typecast)
added test/fixtures/README.md +22 -0
new file mode 100644
@@ -0,0 +1,22 @@
1+# Fixtures
2+
3+Runtime bytecode of two wETH implementations on the allowlist, pulled straight
4+off mainnets so the tests exercise real code rather than a mock that resembles
5+it. `vm.etch` puts one of these at an address and its `EXTCODEHASH` is then, by
6+construction, the value `WETHRegistry` is looking for — which is the only way
7+to test the allowlist without weakening it for tests.
8+
9+| file | source | bytes | codehash |
10+| --- | --- | --- | --- |
11+| `weth9-op-legacy.hex` | Base, `0x4200000000000000000000000000000000000006` | 2041 | `0x8a3a1f6a9f9dce633117adee5b458245835a8645a8c8726a26382a4622508b1c` |
12+| `weth9-canonical.hex` | Ethereum, `0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2` | 3124 | `0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23` |
13+
14+Refetch either with:
15+
16+```shell
17+cast code 0x4200000000000000000000000000000000000006 --rpc-url https://base-rpc.publicnode.com
18+```
19+
20+Both hashes appear in `script/weth-codehashes.sh` output and in the allowlist in
21+`src/WETHRegistry.sol`, so a fixture that has drifted shows up as a test
22+failure rather than as a silent pass.
new file mode 100644
@@ -0,0 +1,22 @@
1+# Fixtures
2+
3+Runtime bytecode of two wETH implementations on the allowlist, pulled straight
4+off mainnets so the tests exercise real code rather than a mock that resembles
5+it. `vm.etch` puts one of these at an address and its `EXTCODEHASH` is then, by
6+construction, the value `WETHRegistry` is looking for — which is the only way
7+to test the allowlist without weakening it for tests.
8+
9+| file | source | bytes | codehash |
10+| --- | --- | --- | --- |
11+| `weth9-op-legacy.hex` | Base, `0x4200000000000000000000000000000000000006` | 2041 | `0x8a3a1f6a9f9dce633117adee5b458245835a8645a8c8726a26382a4622508b1c` |
12+| `weth9-canonical.hex` | Ethereum, `0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2` | 3124 | `0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23` |
13+
14+Refetch either with:
15+
16+```shell
17+cast code 0x4200000000000000000000000000000000000006 --rpc-url https://base-rpc.publicnode.com
18+```
19+
20+Both hashes appear in `script/weth-codehashes.sh` output and in the allowlist in
21+`src/WETHRegistry.sol`, so a fixture that has drifted shows up as a test
22+failure rather than as a silent pass.
added test/fixtures/weth9-canonical.hex +1 -0
new file mode 100644
@@ -0,0 +1 @@
1+0x6060604052600436106100af576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100b9578063095ea7b31461014757806318160ddd146101a157806323b872dd146101ca5780632e1a7d4d14610243578063313ce5671461026657806370a082311461029557806395d89b41146102e2578063a9059cbb14610370578063d0e30db0146103ca578063dd62ed3e146103d4575b6100b7610440565b005b34156100c457600080fd5b6100cc6104dd565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561010c5780820151818401526020810190506100f1565b50505050905090810190601f1680156101395780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561015257600080fd5b610187600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061057b565b604051808215151515815260200191505060405180910390f35b34156101ac57600080fd5b6101b461066d565b6040518082815260200191505060405180910390f35b34156101d557600080fd5b610229600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061068c565b604051808215151515815260200191505060405180910390f35b341561024e57600080fd5b61026460048080359060200190919050506109d9565b005b341561027157600080fd5b610279610b05565b604051808260ff1660ff16815260200191505060405180910390f35b34156102a057600080fd5b6102cc600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610b18565b6040518082815260200191505060405180910390f35b34156102ed57600080fd5b6102f5610b30565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561033557808201518184015260208101905061031a565b50505050905090810190601f1680156103625780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561037b57600080fd5b6103b0600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610bce565b604051808215151515815260200191505060405180910390f35b6103d2610440565b005b34156103df57600080fd5b61042a600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610be3565b6040518082815260200191505060405180910390f35b34600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055503373ffffffffffffffffffffffffffffffffffffffff167fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c346040518082815260200191505060405180910390a2565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156105735780601f1061054857610100808354040283529160200191610573565b820191906000526020600020905b81548152906001019060200180831161055657829003601f168201915b505050505081565b600081600460003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b60003073ffffffffffffffffffffffffffffffffffffffff1631905090565b600081600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101515156106dc57600080fd5b3373ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16141580156107b457507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205414155b156108cf5781600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015151561084457600080fd5b81600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055505b81600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555081600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a3600190509392505050565b80600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410151515610a2757600080fd5b80600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f193505050501515610ab457600080fd5b3373ffffffffffffffffffffffffffffffffffffffff167f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65826040518082815260200191505060405180910390a250565b600260009054906101000a900460ff1681565b60036020528060005260406000206000915090505481565b60018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610bc65780601f10610b9b57610100808354040283529160200191610bc6565b820191906000526020600020905b815481529060010190602001808311610ba957829003601f168201915b505050505081565b6000610bdb33848461068c565b905092915050565b60046020528160005260406000206020528060005260406000206000915091505054815600a165627a7a72305820deb4c2ccab3c2fdca32ab3f46728389c2fe2c165d5fafa07661e4e004f6c344a0029
new file mode 100644
@@ -0,0 +1 @@
1+0x6060604052600436106100af576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100b9578063095ea7b31461014757806318160ddd146101a157806323b872dd146101ca5780632e1a7d4d14610243578063313ce5671461026657806370a082311461029557806395d89b41146102e2578063a9059cbb14610370578063d0e30db0146103ca578063dd62ed3e146103d4575b6100b7610440565b005b34156100c457600080fd5b6100cc6104dd565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561010c5780820151818401526020810190506100f1565b50505050905090810190601f1680156101395780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561015257600080fd5b610187600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061057b565b604051808215151515815260200191505060405180910390f35b34156101ac57600080fd5b6101b461066d565b6040518082815260200191505060405180910390f35b34156101d557600080fd5b610229600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061068c565b604051808215151515815260200191505060405180910390f35b341561024e57600080fd5b61026460048080359060200190919050506109d9565b005b341561027157600080fd5b610279610b05565b604051808260ff1660ff16815260200191505060405180910390f35b34156102a057600080fd5b6102cc600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610b18565b6040518082815260200191505060405180910390f35b34156102ed57600080fd5b6102f5610b30565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561033557808201518184015260208101905061031a565b50505050905090810190601f1680156103625780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561037b57600080fd5b6103b0600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610bce565b604051808215151515815260200191505060405180910390f35b6103d2610440565b005b34156103df57600080fd5b61042a600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610be3565b6040518082815260200191505060405180910390f35b34600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055503373ffffffffffffffffffffffffffffffffffffffff167fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c346040518082815260200191505060405180910390a2565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156105735780601f1061054857610100808354040283529160200191610573565b820191906000526020600020905b81548152906001019060200180831161055657829003601f168201915b505050505081565b600081600460003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b60003073ffffffffffffffffffffffffffffffffffffffff1631905090565b600081600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101515156106dc57600080fd5b3373ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16141580156107b457507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205414155b156108cf5781600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015151561084457600080fd5b81600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055505b81600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555081600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a3600190509392505050565b80600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410151515610a2757600080fd5b80600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f193505050501515610ab457600080fd5b3373ffffffffffffffffffffffffffffffffffffffff167f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65826040518082815260200191505060405180910390a250565b600260009054906101000a900460ff1681565b60036020528060005260406000206000915090505481565b60018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610bc65780601f10610b9b57610100808354040283529160200191610bc6565b820191906000526020600020905b815481529060010190602001808311610ba957829003601f168201915b505050505081565b6000610bdb33848461068c565b905092915050565b60046020528160005260406000206020528060005260406000206000915091505054815600a165627a7a72305820deb4c2ccab3c2fdca32ab3f46728389c2fe2c165d5fafa07661e4e004f6c344a0029
added test/fixtures/weth9-op-legacy.hex +1 -0
new file mode 100644
@@ -0,0 +1 @@
1+0x6080604052600436106100bc5760003560e01c8063313ce56711610074578063a9059cbb1161004e578063a9059cbb146102cb578063d0e30db0146100bc578063dd62ed3e14610311576100bc565b8063313ce5671461024b57806370a082311461027657806395d89b41146102b6576100bc565b806318160ddd116100a557806318160ddd146101aa57806323b872dd146101d15780632e1a7d4d14610221576100bc565b806306fdde03146100c6578063095ea7b314610150575b6100c4610359565b005b3480156100d257600080fd5b506100db6103a8565b6040805160208082528351818301528351919283929083019185019080838360005b838110156101155781810151838201526020016100fd565b50505050905090810190601f1680156101425780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561015c57600080fd5b506101966004803603604081101561017357600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135610454565b604080519115158252519081900360200190f35b3480156101b657600080fd5b506101bf6104c7565b60408051918252519081900360200190f35b3480156101dd57600080fd5b50610196600480360360608110156101f457600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135811691602081013590911690604001356104cb565b34801561022d57600080fd5b506100c46004803603602081101561024457600080fd5b503561066b565b34801561025757600080fd5b50610260610700565b6040805160ff9092168252519081900360200190f35b34801561028257600080fd5b506101bf6004803603602081101561029957600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16610709565b3480156102c257600080fd5b506100db61071b565b3480156102d757600080fd5b50610196600480360360408110156102ee57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135610793565b34801561031d57600080fd5b506101bf6004803603604081101561033457600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160200135166107a7565b33600081815260036020908152604091829020805434908101909155825190815291517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c9281900390910190a2565b6000805460408051602060026001851615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f8101849004840282018401909252818152929183018282801561044c5780601f106104215761010080835404028352916020019161044c565b820191906000526020600020905b81548152906001019060200180831161042f57829003601f168201915b505050505081565b33600081815260046020908152604080832073ffffffffffffffffffffffffffffffffffffffff8716808552908352818420869055815186815291519394909390927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925928290030190a350600192915050565b4790565b73ffffffffffffffffffffffffffffffffffffffff83166000908152600360205260408120548211156104fd57600080fd5b73ffffffffffffffffffffffffffffffffffffffff84163314801590610573575073ffffffffffffffffffffffffffffffffffffffff841660009081526004602090815260408083203384529091529020547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff14155b156105ed5773ffffffffffffffffffffffffffffffffffffffff841660009081526004602090815260408083203384529091529020548211156105b557600080fd5b73ffffffffffffffffffffffffffffffffffffffff841660009081526004602090815260408083203384529091529020805483900390555b73ffffffffffffffffffffffffffffffffffffffff808516600081815260036020908152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a35060019392505050565b3360009081526003602052604090205481111561068757600080fd5b33600081815260036020526040808220805485900390555183156108fc0291849190818181858888f193505050501580156106c6573d6000803e3d6000fd5b5060408051828152905133917f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65919081900360200190a250565b60025460ff1681565b60036020526000908152604090205481565b60018054604080516020600284861615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f8101849004840282018401909252818152929183018282801561044c5780601f106104215761010080835404028352916020019161044c565b60006107a03384846104cb565b9392505050565b60046020908152600092835260408084209091529082529020548156fea265627a7a72315820e496abb80c5983b030f680d0bd88f66bf44e261bc3be070d612dd72f9f1f5e9a64736f6c63430005110032
new file mode 100644
@@ -0,0 +1 @@
1+0x6080604052600436106100bc5760003560e01c8063313ce56711610074578063a9059cbb1161004e578063a9059cbb146102cb578063d0e30db0146100bc578063dd62ed3e14610311576100bc565b8063313ce5671461024b57806370a082311461027657806395d89b41146102b6576100bc565b806318160ddd116100a557806318160ddd146101aa57806323b872dd146101d15780632e1a7d4d14610221576100bc565b806306fdde03146100c6578063095ea7b314610150575b6100c4610359565b005b3480156100d257600080fd5b506100db6103a8565b6040805160208082528351818301528351919283929083019185019080838360005b838110156101155781810151838201526020016100fd565b50505050905090810190601f1680156101425780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561015c57600080fd5b506101966004803603604081101561017357600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135610454565b604080519115158252519081900360200190f35b3480156101b657600080fd5b506101bf6104c7565b60408051918252519081900360200190f35b3480156101dd57600080fd5b50610196600480360360608110156101f457600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135811691602081013590911690604001356104cb565b34801561022d57600080fd5b506100c46004803603602081101561024457600080fd5b503561066b565b34801561025757600080fd5b50610260610700565b6040805160ff9092168252519081900360200190f35b34801561028257600080fd5b506101bf6004803603602081101561029957600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16610709565b3480156102c257600080fd5b506100db61071b565b3480156102d757600080fd5b50610196600480360360408110156102ee57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135610793565b34801561031d57600080fd5b506101bf6004803603604081101561033457600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160200135166107a7565b33600081815260036020908152604091829020805434908101909155825190815291517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c9281900390910190a2565b6000805460408051602060026001851615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f8101849004840282018401909252818152929183018282801561044c5780601f106104215761010080835404028352916020019161044c565b820191906000526020600020905b81548152906001019060200180831161042f57829003601f168201915b505050505081565b33600081815260046020908152604080832073ffffffffffffffffffffffffffffffffffffffff8716808552908352818420869055815186815291519394909390927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925928290030190a350600192915050565b4790565b73ffffffffffffffffffffffffffffffffffffffff83166000908152600360205260408120548211156104fd57600080fd5b73ffffffffffffffffffffffffffffffffffffffff84163314801590610573575073ffffffffffffffffffffffffffffffffffffffff841660009081526004602090815260408083203384529091529020547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff14155b156105ed5773ffffffffffffffffffffffffffffffffffffffff841660009081526004602090815260408083203384529091529020548211156105b557600080fd5b73ffffffffffffffffffffffffffffffffffffffff841660009081526004602090815260408083203384529091529020805483900390555b73ffffffffffffffffffffffffffffffffffffffff808516600081815260036020908152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a35060019392505050565b3360009081526003602052604090205481111561068757600080fd5b33600081815260036020526040808220805485900390555183156108fc0291849190818181858888f193505050501580156106c6573d6000803e3d6000fd5b5060408051828152905133917f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65919081900360200190a250565b60025460ff1681565b60036020526000908152604090205481565b60018054604080516020600284861615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f8101849004840282018401909252818152929183018282801561044c5780601f106104215761010080835404028352916020019161044c565b60006107a03384846104cb565b9392505050565b60046020908152600092835260408084209091529082529020548156fea265627a7a72315820e496abb80c5983b030f680d0bd88f66bf44e261bc3be070d612dd72f9f1f5e9a64736f6c63430005110032