Merge 10-fix-weth-chain-allowlist into main
bb4dfe3 parent: a62812f modified
docs/MULTICHAIN.md +1 -1 | @@ -22,7 +22,7 @@ The part that makes this non-trivial: `Love` still needs to know which [wETH](ht | ||
| 22 | 22 | |
| 23 | 23 | ## The two-contract setup |
| 24 | 24 | |
| 25 | -`WETHRegistry` deploys first, with a fixed salt (`keccak256("LOVE.WETHRegistry")`), landing at the same address on every chain: `0x9Cf17430fEdEC487518416D1Cdc849b1eE9CDbA3`, hardcoded into `Love` as `REGISTRY`. | |
| 25 | +`WETHRegistry` deploys first, with a fixed salt (`keccak256("LOVE.WETHRegistry")`), landing at the same address on every chain: `0x33dF1Cd28c9d577224F9A79dD9541275BCa70998`, hardcoded into `Love` as `REGISTRY`. | |
| 26 | 26 | |
| 27 | 27 | `register(candidate)` is permissionless, payable exactly `1 wei`, and accepts a candidate only if it passes two gates: |
| 28 | 28 | |
| @@ -22,7 +22,7 @@ The part that makes this non-trivial: `Love` still needs to know which [wETH](ht | |||
| 22 | 22 | ||
| 23 | ## The two-contract setup | 23 | ## The two-contract setup |
| 24 | 24 | ||
| 25 | -`WETHRegistry` deploys first, with a fixed salt (`keccak256("LOVE.WETHRegistry")`), landing at the same address on every chain: `0x9Cf17430fEdEC487518416D1Cdc849b1eE9CDbA3`, hardcoded into `Love` as `REGISTRY`. | 25 | +`WETHRegistry` deploys first, with a fixed salt (`keccak256("LOVE.WETHRegistry")`), landing at the same address on every chain: `0x33dF1Cd28c9d577224F9A79dD9541275BCa70998`, hardcoded into `Love` as `REGISTRY`. |
| 26 | 26 | ||
| 27 | `register(candidate)` is permissionless, payable exactly `1 wei`, and accepts a candidate only if it passes two gates: | 27 | `register(candidate)` is permissionless, payable exactly `1 wei`, and accepts a candidate only if it passes two gates: |
| 28 | 28 | ||
modified
src/Love.sol +1 -1 | @@ -35,7 +35,7 @@ contract Love is ERC20 { | ||
| 35 | 35 | /// `WETHRegistry`, the solc version or the optimizer settings change. |
| 36 | 36 | /// `LoveRegistryAddressTest` recomputes it and fails if this constant |
| 37 | 37 | /// has drifted. |
| 38 | - address public constant REGISTRY = 0x9Cf17430fEdEC487518416D1Cdc849b1eE9CDbA3; | |
| 38 | + address public constant REGISTRY = 0x33dF1Cd28c9d577224F9A79dD9541275BCa70998; | |
| 39 | 39 | |
| 40 | 40 | /// @notice The wETH this token is pegged to and collateralised with. |
| 41 | 41 | /// @dev Read once from the registry and immutable thereafter. The registry |
| @@ -35,7 +35,7 @@ contract Love is ERC20 { | |||
| 35 | /// `WETHRegistry`, the solc version or the optimizer settings change. | 35 | /// `WETHRegistry`, the solc version or the optimizer settings change. |
| 36 | /// `LoveRegistryAddressTest` recomputes it and fails if this constant | 36 | /// `LoveRegistryAddressTest` recomputes it and fails if this constant |
| 37 | /// has drifted. | 37 | /// has drifted. |
| 38 | - address public constant REGISTRY = 0x9Cf17430fEdEC487518416D1Cdc849b1eE9CDbA3; | 38 | + address public constant REGISTRY = 0x33dF1Cd28c9d577224F9A79dD9541275BCa70998; |
| 39 | 39 | ||
| 40 | /// @notice The wETH this token is pegged to and collateralised with. | 40 | /// @notice The wETH this token is pegged to and collateralised with. |
| 41 | /// @dev Read once from the registry and immutable thereafter. The registry | 41 | /// @dev Read once from the registry and immutable thereafter. The registry |
modified
src/WETHRegistry.sol +78 -27 | @@ -17,15 +17,23 @@ import {IWETH} from "./IWETH.sol"; | ||
| 17 | 17 | /// |
| 18 | 18 | /// Two gates run at registration: |
| 19 | 19 | /// |
| 20 | -/// 1. `EXTCODEHASH` against the allowlist below. This is the real | |
| 21 | -/// defence: matching means the candidate is provably one of a handful | |
| 22 | -/// of implementations whose source has been read, not merely something | |
| 23 | -/// that behaves well today. It is what catches a hidden mint, an | |
| 24 | -/// upgrade hook or a backdoor, none of which a behavioural test can | |
| 25 | -/// see. | |
| 20 | +/// 1. `EXTCODEHASH` against the allowlist below, paired with the current | |
| 21 | +/// `block.chainid`. This is the real defence: matching means the | |
| 22 | +/// candidate is provably one of a handful of implementations whose | |
| 23 | +/// source has been read *and reviewed for this chain* — not merely | |
| 24 | +/// something that behaves well today, and not merely bytecode that | |
| 25 | +/// happens to be byte-identical to something reviewed elsewhere. | |
| 26 | +/// Pairing with the chain ID is what stops a chain-agnostic | |
| 27 | +/// implementation like canonical WETH9 — no constructor argument, no | |
| 28 | +/// chain-specific logic, so its bytecode is identical everywhere — | |
| 29 | +/// from being accepted on a chain whose native currency isn't ETH at | |
| 30 | +/// all, where it would still wrap correctly, just the wrong asset. | |
| 26 | 31 | /// 2. A one-wei deposit/withdraw round trip, which catches the plain |
| 27 | 32 | /// mistake of an address that is not wETH at all, and proves the |
| 28 | -/// wrapper actually works on this chain. | |
| 33 | +/// wrapper actually works on this chain. It cannot, by itself, prove | |
| 34 | +/// the native currency being wrapped is ETH: that fact isn't | |
| 35 | +/// observable from inside the EVM, which is exactly why gate 1 has to | |
| 36 | +/// carry the chain ID. | |
| 29 | 37 | /// |
| 30 | 38 | /// The registration is write-once. There is no setter, no owner and no |
| 31 | 39 | /// way to revoke an entry, so a `Love` deployed against this registry can |
| @@ -61,10 +69,11 @@ contract WETHRegistry { | ||
| 61 | 69 | /// @param registered The wETH registered by the earlier call. |
| 62 | 70 | error AlreadyRegistered(IWETH registered); |
| 63 | 71 | |
| 64 | - /// @notice Thrown when the candidate's code is not a reviewed wETH. | |
| 72 | + /// @notice Thrown when the candidate's code is not a reviewed wETH on this chain. | |
| 65 | 73 | /// @param candidate The rejected address. |
| 66 | 74 | /// @param codeHash Its `EXTCODEHASH`, zero if there is no code there. |
| 67 | - error UnknownImplementation(IWETH candidate, bytes32 codeHash); | |
| 75 | + /// @param chainId The chain the candidate was rejected on. | |
| 76 | + error UnknownImplementation(IWETH candidate, bytes32 codeHash, uint256 chainId); | |
| 68 | 77 | |
| 69 | 78 | /// @notice Thrown when the call does not carry exactly `PROBE` wei. |
| 70 | 79 | /// @param sent The value that came with the call. |
| @@ -115,7 +124,9 @@ contract WETHRegistry { | ||
| 115 | 124 | if (msg.value != PROBE) revert ProbeValueRequired(msg.value); |
| 116 | 125 | |
| 117 | 126 | bytes32 codeHash = address(candidate).codehash; |
| 118 | - if (!isKnownImplementation(codeHash)) revert UnknownImplementation(candidate, codeHash); | |
| 127 | + if (!isKnownImplementation(codeHash, block.chainid)) { | |
| 128 | + revert UnknownImplementation(candidate, codeHash, block.chainid); | |
| 129 | + } | |
| 119 | 130 | |
| 120 | 131 | _probe(candidate); |
| 121 | 132 | |
| @@ -125,7 +136,7 @@ contract WETHRegistry { | ||
| 125 | 136 | return candidate; |
| 126 | 137 | } |
| 127 | 138 | |
| 128 | - /// @notice Whether `codeHash` is one of the reviewed wETH implementations. | |
| 139 | + /// @notice Whether `codeHash` is a reviewed wETH implementation on `chainId`. | |
| 129 | 140 | /// @dev Compile-time, so the list is ownerless and append-only by |
| 130 | 141 | /// construction: extending it means publishing a new registry, which |
| 131 | 142 | /// leaves every existing deployment exactly as it was. No key can |
| @@ -140,32 +151,72 @@ contract WETHRegistry { | ||
| 140 | 151 | /// holding for every family added later. Ten constants is the cheaper |
| 141 | 152 | /// side of that trade. |
| 142 | 153 | /// |
| 143 | - /// Grouped by implementation. Every value is reproducible from chain | |
| 144 | - /// state with `script/weth-codehashes.sh`. | |
| 154 | + /// Paired with `chainId` rather than accepted on any chain that | |
| 155 | + /// reproduces the hash: some of these implementations (canonical | |
| 156 | + /// WETH9 above all — no constructor argument, no reference to | |
| 157 | + /// anything chain-specific) compile to the exact same bytecode | |
| 158 | + /// wherever they're deployed. Accepting the hash alone would mean a | |
| 159 | + /// chain whose native currency isn't ETH could register a candidate | |
| 160 | + /// that wraps its native currency flawlessly — the round-trip probe | |
| 161 | + /// would pass — while wrapping the wrong asset entirely, since | |
| 162 | + /// nothing observable from inside the EVM distinguishes "native | |
| 163 | + /// currency is ETH" from "native currency behaves like ETH." Pairing | |
| 164 | + /// the hash with the specific chain IDs it was actually reviewed on | |
| 165 | + /// closes that gap: a hash match now means "reviewed on this chain," | |
| 166 | + /// not "reviewed somewhere." | |
| 167 | + /// | |
| 168 | + /// Grouped by implementation. Every codehash is reproducible from | |
| 169 | + /// chain state with `script/weth-codehashes.sh`; the paired chain IDs | |
| 170 | + /// are exactly the chains that script read that hash from. | |
| 145 | 171 | /// @param codeHash The `EXTCODEHASH` to check. |
| 146 | - /// @return True if a candidate with this code may be registered. | |
| 147 | - function isKnownImplementation(bytes32 codeHash) public pure returns (bool) { | |
| 172 | + /// @param chainId The chain the candidate was found on, i.e. `block.chainid`. | |
| 173 | + /// @return True if a candidate with this code may be registered on this chain. | |
| 174 | + function isKnownImplementation(bytes32 codeHash, uint256 chainId) public pure returns (bool) { | |
| 148 | 175 | // OP Stack legacy WETH9, 2041 bytes, solc 0.5.17. One implementation, |
| 149 | 176 | // four hashes: these chains run byte-identical code and differ only |
| 150 | 177 | // inside solc's metadata blob, which never executes. |
| 151 | - if (codeHash == 0x779bbf2a738ef09d961c945116197e2ac764c1b39304b2b4418cd4e42668b173) return true; // optimism | |
| 152 | - if (codeHash == 0x8a3a1f6a9f9dce633117adee5b458245835a8645a8c8726a26382a4622508b1c) return true; // base, mode, zora | |
| 153 | - if (codeHash == 0x557c8e14d33f7cd67cad0141e1a49ebf3488a447fc3df7aa66b127778a0383d1) return true; // world-chain | |
| 154 | - if (codeHash == 0xf35fe602ba2a3b96f2e27ff7c8b8010800a8d0d616a5fb1f902e087b590355f3) return true; // lisk | |
| 155 | - | |
| 156 | - // Canonical WETH9, 3124 bytes, solc 0.4.19. | |
| 157 | - if (codeHash == 0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23) return true; // ethereum | |
| 158 | - if (codeHash == 0xa670ec6c272ddec6d328d6f3d5cad65a841a6ab45e8e5cf825150eb458be4f1f) return true; // linea | |
| 159 | - if (codeHash == 0x032e9cab14331328530468e54f1b91777b4d5c9dbbb400884badb32bc4113585) return true; // polygon-zkevm | |
| 178 | + if (codeHash == 0x779bbf2a738ef09d961c945116197e2ac764c1b39304b2b4418cd4e42668b173) { | |
| 179 | + return chainId == 10; // optimism | |
| 180 | + } | |
| 181 | + if (codeHash == 0x8a3a1f6a9f9dce633117adee5b458245835a8645a8c8726a26382a4622508b1c) { | |
| 182 | + return chainId == 8453 || chainId == 34_443 || chainId == 7_777_777; // base, mode, zora | |
| 183 | + } | |
| 184 | + if (codeHash == 0x557c8e14d33f7cd67cad0141e1a49ebf3488a447fc3df7aa66b127778a0383d1) { | |
| 185 | + return chainId == 480; // world-chain | |
| 186 | + } | |
| 187 | + if (codeHash == 0xf35fe602ba2a3b96f2e27ff7c8b8010800a8d0d616a5fb1f902e087b590355f3) { | |
| 188 | + return chainId == 1135; // lisk | |
| 189 | + } | |
| 190 | + | |
| 191 | + // Canonical WETH9, 3124 bytes, solc 0.4.19. Chain-agnostic bytecode — | |
| 192 | + // no constructor argument, nothing chain-specific — so it reproduces | |
| 193 | + // identically anywhere; the chain ID is the only thing standing | |
| 194 | + // between "reviewed" and "byte-identical to something reviewed | |
| 195 | + // elsewhere." | |
| 196 | + if (codeHash == 0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23) { | |
| 197 | + return chainId == 1; // ethereum | |
| 198 | + } | |
| 199 | + if (codeHash == 0xa670ec6c272ddec6d328d6f3d5cad65a841a6ab45e8e5cf825150eb458be4f1f) { | |
| 200 | + return chainId == 59_144; // linea | |
| 201 | + } | |
| 202 | + if (codeHash == 0x032e9cab14331328530468e54f1b91777b4d5c9dbbb400884badb32bc4113585) { | |
| 203 | + return chainId == 1101; // polygon-zkevm | |
| 204 | + } | |
| 160 | 205 | |
| 161 | 206 | // OP Stack WETH, 2865 bytes, solc 0.8.15. |
| 162 | - if (codeHash == 0xd0f1614c5dacfbd34f1c6f500f397009e4c9a8bfd4e02db353edb2253d9a8012) return true; // unichain, soneium, ink | |
| 207 | + if (codeHash == 0xd0f1614c5dacfbd34f1c6f500f397009e4c9a8bfd4e02db353edb2253d9a8012) { | |
| 208 | + return chainId == 130 || chainId == 1868 || chainId == 57_073; // unichain, soneium, ink | |
| 209 | + } | |
| 163 | 210 | |
| 164 | 211 | // Taiko, 3204 bytes. |
| 165 | - if (codeHash == 0x9f3d95086909fce850d997158aba31abe26c3aad6a413107ca0bf9d53a7c42e9) return true; // taiko | |
| 212 | + if (codeHash == 0x9f3d95086909fce850d997158aba31abe26c3aad6a413107ca0bf9d53a7c42e9) { | |
| 213 | + return chainId == 167_000; // taiko | |
| 214 | + } | |
| 166 | 215 | |
| 167 | 216 | // Scroll, 5871 bytes. |
| 168 | - if (codeHash == 0xe8c4073351c26b9831c1e5af153b9be4713a4af9edfdf32b58077b735e120f14) return true; // scroll | |
| 217 | + if (codeHash == 0xe8c4073351c26b9831c1e5af153b9be4713a4af9edfdf32b58077b735e120f14) { | |
| 218 | + return chainId == 534_352; // scroll | |
| 219 | + } | |
| 169 | 220 | |
| 170 | 221 | return false; |
| 171 | 222 | } |
| @@ -17,15 +17,23 @@ import {IWETH} from "./IWETH.sol"; | |||
| 17 | /// | 17 | /// |
| 18 | /// Two gates run at registration: | 18 | /// Two gates run at registration: |
| 19 | /// | 19 | /// |
| 20 | -/// 1. `EXTCODEHASH` against the allowlist below. This is the real | 20 | +/// 1. `EXTCODEHASH` against the allowlist below, paired with the current |
| 21 | -/// defence: matching means the candidate is provably one of a handful | 21 | +/// `block.chainid`. This is the real defence: matching means the |
| 22 | -/// of implementations whose source has been read, not merely something | 22 | +/// candidate is provably one of a handful of implementations whose |
| 23 | -/// that behaves well today. It is what catches a hidden mint, an | 23 | +/// source has been read *and reviewed for this chain* — not merely |
| 24 | -/// upgrade hook or a backdoor, none of which a behavioural test can | 24 | +/// something that behaves well today, and not merely bytecode that |
| 25 | -/// see. | 25 | +/// happens to be byte-identical to something reviewed elsewhere. |
| 26 | +/// Pairing with the chain ID is what stops a chain-agnostic | ||
| 27 | +/// implementation like canonical WETH9 — no constructor argument, no | ||
| 28 | +/// chain-specific logic, so its bytecode is identical everywhere — | ||
| 29 | +/// from being accepted on a chain whose native currency isn't ETH at | ||
| 30 | +/// all, where it would still wrap correctly, just the wrong asset. | ||
| 26 | /// 2. A one-wei deposit/withdraw round trip, which catches the plain | 31 | /// 2. A one-wei deposit/withdraw round trip, which catches the plain |
| 27 | /// mistake of an address that is not wETH at all, and proves the | 32 | /// mistake of an address that is not wETH at all, and proves the |
| 28 | -/// wrapper actually works on this chain. | 33 | +/// wrapper actually works on this chain. It cannot, by itself, prove |
| 34 | +/// the native currency being wrapped is ETH: that fact isn't | ||
| 35 | +/// observable from inside the EVM, which is exactly why gate 1 has to | ||
| 36 | +/// carry the chain ID. | ||
| 29 | /// | 37 | /// |
| 30 | /// The registration is write-once. There is no setter, no owner and no | 38 | /// The registration is write-once. There is no setter, no owner and no |
| 31 | /// way to revoke an entry, so a `Love` deployed against this registry can | 39 | /// way to revoke an entry, so a `Love` deployed against this registry can |
| @@ -61,10 +69,11 @@ contract WETHRegistry { | |||
| 61 | /// @param registered The wETH registered by the earlier call. | 69 | /// @param registered The wETH registered by the earlier call. |
| 62 | error AlreadyRegistered(IWETH registered); | 70 | error AlreadyRegistered(IWETH registered); |
| 63 | 71 | ||
| 64 | - /// @notice Thrown when the candidate's code is not a reviewed wETH. | 72 | + /// @notice Thrown when the candidate's code is not a reviewed wETH on this chain. |
| 65 | /// @param candidate The rejected address. | 73 | /// @param candidate The rejected address. |
| 66 | /// @param codeHash Its `EXTCODEHASH`, zero if there is no code there. | 74 | /// @param codeHash Its `EXTCODEHASH`, zero if there is no code there. |
| 67 | - error UnknownImplementation(IWETH candidate, bytes32 codeHash); | 75 | + /// @param chainId The chain the candidate was rejected on. |
| 76 | + error UnknownImplementation(IWETH candidate, bytes32 codeHash, uint256 chainId); | ||
| 68 | 77 | ||
| 69 | /// @notice Thrown when the call does not carry exactly `PROBE` wei. | 78 | /// @notice Thrown when the call does not carry exactly `PROBE` wei. |
| 70 | /// @param sent The value that came with the call. | 79 | /// @param sent The value that came with the call. |
| @@ -115,7 +124,9 @@ contract WETHRegistry { | |||
| 115 | if (msg.value != PROBE) revert ProbeValueRequired(msg.value); | 124 | if (msg.value != PROBE) revert ProbeValueRequired(msg.value); |
| 116 | 125 | ||
| 117 | bytes32 codeHash = address(candidate).codehash; | 126 | bytes32 codeHash = address(candidate).codehash; |
| 118 | - if (!isKnownImplementation(codeHash)) revert UnknownImplementation(candidate, codeHash); | 127 | + if (!isKnownImplementation(codeHash, block.chainid)) { |
| 128 | + revert UnknownImplementation(candidate, codeHash, block.chainid); | ||
| 129 | + } | ||
| 119 | 130 | ||
| 120 | _probe(candidate); | 131 | _probe(candidate); |
| 121 | 132 | ||
| @@ -125,7 +136,7 @@ contract WETHRegistry { | |||
| 125 | return candidate; | 136 | return candidate; |
| 126 | } | 137 | } |
| 127 | 138 | ||
| 128 | - /// @notice Whether `codeHash` is one of the reviewed wETH implementations. | 139 | + /// @notice Whether `codeHash` is a reviewed wETH implementation on `chainId`. |
| 129 | /// @dev Compile-time, so the list is ownerless and append-only by | 140 | /// @dev Compile-time, so the list is ownerless and append-only by |
| 130 | /// construction: extending it means publishing a new registry, which | 141 | /// construction: extending it means publishing a new registry, which |
| 131 | /// leaves every existing deployment exactly as it was. No key can | 142 | /// leaves every existing deployment exactly as it was. No key can |
| @@ -140,32 +151,72 @@ contract WETHRegistry { | |||
| 140 | /// holding for every family added later. Ten constants is the cheaper | 151 | /// holding for every family added later. Ten constants is the cheaper |
| 141 | /// side of that trade. | 152 | /// side of that trade. |
| 142 | /// | 153 | /// |
| 143 | - /// Grouped by implementation. Every value is reproducible from chain | 154 | + /// Paired with `chainId` rather than accepted on any chain that |
| 144 | - /// state with `script/weth-codehashes.sh`. | 155 | + /// reproduces the hash: some of these implementations (canonical |
| 156 | + /// WETH9 above all — no constructor argument, no reference to | ||
| 157 | + /// anything chain-specific) compile to the exact same bytecode | ||
| 158 | + /// wherever they're deployed. Accepting the hash alone would mean a | ||
| 159 | + /// chain whose native currency isn't ETH could register a candidate | ||
| 160 | + /// that wraps its native currency flawlessly — the round-trip probe | ||
| 161 | + /// would pass — while wrapping the wrong asset entirely, since | ||
| 162 | + /// nothing observable from inside the EVM distinguishes "native | ||
| 163 | + /// currency is ETH" from "native currency behaves like ETH." Pairing | ||
| 164 | + /// the hash with the specific chain IDs it was actually reviewed on | ||
| 165 | + /// closes that gap: a hash match now means "reviewed on this chain," | ||
| 166 | + /// not "reviewed somewhere." | ||
| 167 | + /// | ||
| 168 | + /// Grouped by implementation. Every codehash is reproducible from | ||
| 169 | + /// chain state with `script/weth-codehashes.sh`; the paired chain IDs | ||
| 170 | + /// are exactly the chains that script read that hash from. | ||
| 145 | /// @param codeHash The `EXTCODEHASH` to check. | 171 | /// @param codeHash The `EXTCODEHASH` to check. |
| 146 | - /// @return True if a candidate with this code may be registered. | 172 | + /// @param chainId The chain the candidate was found on, i.e. `block.chainid`. |
| 147 | - function isKnownImplementation(bytes32 codeHash) public pure returns (bool) { | 173 | + /// @return True if a candidate with this code may be registered on this chain. |
| 174 | + function isKnownImplementation(bytes32 codeHash, uint256 chainId) public pure returns (bool) { | ||
| 148 | // OP Stack legacy WETH9, 2041 bytes, solc 0.5.17. One implementation, | 175 | // OP Stack legacy WETH9, 2041 bytes, solc 0.5.17. One implementation, |
| 149 | // four hashes: these chains run byte-identical code and differ only | 176 | // four hashes: these chains run byte-identical code and differ only |
| 150 | // inside solc's metadata blob, which never executes. | 177 | // inside solc's metadata blob, which never executes. |
| 151 | - if (codeHash == 0x779bbf2a738ef09d961c945116197e2ac764c1b39304b2b4418cd4e42668b173) return true; // optimism | 178 | + if (codeHash == 0x779bbf2a738ef09d961c945116197e2ac764c1b39304b2b4418cd4e42668b173) { |
| 152 | - if (codeHash == 0x8a3a1f6a9f9dce633117adee5b458245835a8645a8c8726a26382a4622508b1c) return true; // base, mode, zora | 179 | + return chainId == 10; // optimism |
| 153 | - if (codeHash == 0x557c8e14d33f7cd67cad0141e1a49ebf3488a447fc3df7aa66b127778a0383d1) return true; // world-chain | 180 | + } |
| 154 | - if (codeHash == 0xf35fe602ba2a3b96f2e27ff7c8b8010800a8d0d616a5fb1f902e087b590355f3) return true; // lisk | 181 | + if (codeHash == 0x8a3a1f6a9f9dce633117adee5b458245835a8645a8c8726a26382a4622508b1c) { |
| 155 | - | 182 | + return chainId == 8453 || chainId == 34_443 || chainId == 7_777_777; // base, mode, zora |
| 156 | - // Canonical WETH9, 3124 bytes, solc 0.4.19. | 183 | + } |
| 157 | - if (codeHash == 0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23) return true; // ethereum | 184 | + if (codeHash == 0x557c8e14d33f7cd67cad0141e1a49ebf3488a447fc3df7aa66b127778a0383d1) { |
| 158 | - if (codeHash == 0xa670ec6c272ddec6d328d6f3d5cad65a841a6ab45e8e5cf825150eb458be4f1f) return true; // linea | 185 | + return chainId == 480; // world-chain |
| 159 | - if (codeHash == 0x032e9cab14331328530468e54f1b91777b4d5c9dbbb400884badb32bc4113585) return true; // polygon-zkevm | 186 | + } |
| 187 | + if (codeHash == 0xf35fe602ba2a3b96f2e27ff7c8b8010800a8d0d616a5fb1f902e087b590355f3) { | ||
| 188 | + return chainId == 1135; // lisk | ||
| 189 | + } | ||
| 190 | + | ||
| 191 | + // Canonical WETH9, 3124 bytes, solc 0.4.19. Chain-agnostic bytecode — | ||
| 192 | + // no constructor argument, nothing chain-specific — so it reproduces | ||
| 193 | + // identically anywhere; the chain ID is the only thing standing | ||
| 194 | + // between "reviewed" and "byte-identical to something reviewed | ||
| 195 | + // elsewhere." | ||
| 196 | + if (codeHash == 0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23) { | ||
| 197 | + return chainId == 1; // ethereum | ||
| 198 | + } | ||
| 199 | + if (codeHash == 0xa670ec6c272ddec6d328d6f3d5cad65a841a6ab45e8e5cf825150eb458be4f1f) { | ||
| 200 | + return chainId == 59_144; // linea | ||
| 201 | + } | ||
| 202 | + if (codeHash == 0x032e9cab14331328530468e54f1b91777b4d5c9dbbb400884badb32bc4113585) { | ||
| 203 | + return chainId == 1101; // polygon-zkevm | ||
| 204 | + } | ||
| 160 | 205 | ||
| 161 | // OP Stack WETH, 2865 bytes, solc 0.8.15. | 206 | // OP Stack WETH, 2865 bytes, solc 0.8.15. |
| 162 | - if (codeHash == 0xd0f1614c5dacfbd34f1c6f500f397009e4c9a8bfd4e02db353edb2253d9a8012) return true; // unichain, soneium, ink | 207 | + if (codeHash == 0xd0f1614c5dacfbd34f1c6f500f397009e4c9a8bfd4e02db353edb2253d9a8012) { |
| 208 | + return chainId == 130 || chainId == 1868 || chainId == 57_073; // unichain, soneium, ink | ||
| 209 | + } | ||
| 163 | 210 | ||
| 164 | // Taiko, 3204 bytes. | 211 | // Taiko, 3204 bytes. |
| 165 | - if (codeHash == 0x9f3d95086909fce850d997158aba31abe26c3aad6a413107ca0bf9d53a7c42e9) return true; // taiko | 212 | + if (codeHash == 0x9f3d95086909fce850d997158aba31abe26c3aad6a413107ca0bf9d53a7c42e9) { |
| 213 | + return chainId == 167_000; // taiko | ||
| 214 | + } | ||
| 166 | 215 | ||
| 167 | // Scroll, 5871 bytes. | 216 | // Scroll, 5871 bytes. |
| 168 | - if (codeHash == 0xe8c4073351c26b9831c1e5af153b9be4713a4af9edfdf32b58077b735e120f14) return true; // scroll | 217 | + if (codeHash == 0xe8c4073351c26b9831c1e5af153b9be4713a4af9edfdf32b58077b735e120f14) { |
| 218 | + return chainId == 534_352; // scroll | ||
| 219 | + } | ||
| 169 | 220 | ||
| 170 | return false; | 221 | return false; |
| 171 | } | 222 | } |
modified
test/Fixtures.sol +19 -1 | @@ -22,9 +22,17 @@ abstract contract Fixtures is CommonBase, StdCheats { | ||
| 22 | 22 | /// @notice OP Stack legacy WETH9, as deployed on Base, Mode and Zora. |
| 23 | 23 | string internal constant WETH9_OP_LEGACY = "test/fixtures/weth9-op-legacy.hex"; |
| 24 | 24 | |
| 25 | + /// @notice A chain WETH9_OP_LEGACY's codehash is reviewed for. | |
| 26 | + /// @dev Base; Mode and Zora share the same codehash and would do equally | |
| 27 | + /// well here, since `isKnownImplementation` accepts all three. | |
| 28 | + uint256 internal constant WETH9_OP_LEGACY_CHAIN_ID = 8453; | |
| 29 | + | |
| 25 | 30 | /// @notice Canonical WETH9, as deployed on Ethereum mainnet. |
| 26 | 31 | string internal constant WETH9_CANONICAL = "test/fixtures/weth9-canonical.hex"; |
| 27 | 32 | |
| 33 | + /// @notice The chain WETH9_CANONICAL's codehash is reviewed for. | |
| 34 | + uint256 internal constant WETH9_CANONICAL_CHAIN_ID = 1; | |
| 35 | + | |
| 28 | 36 | /// @notice Put real wETH runtime bytecode at `where`. |
| 29 | 37 | /// @dev Reads the fixture rather than embedding it, so refreshing a |
| 30 | 38 | /// fixture from chain state does not mean editing Solidity. |
| @@ -66,11 +74,21 @@ abstract contract Fixtures is CommonBase, StdCheats { | ||
| 66 | 74 | } |
| 67 | 75 | |
| 68 | 76 | /// @notice The whole chain in one call: registry, wETH, registration. |
| 77 | + /// @dev Sets `block.chainid` to `chainId` first — the registry now checks | |
| 78 | + /// the candidate's codehash against the chain it's being registered | |
| 79 | + /// on, not just against the allowlist in isolation, so a test has to | |
| 80 | + /// put itself on a chain the fixture's codehash is actually reviewed | |
| 81 | + /// for. Use one of the `WETH9_*_CHAIN_ID` constants above. | |
| 69 | 82 | /// @param wethAt The address to etch wETH at. |
| 70 | 83 | /// @param fixture Which implementation to etch. |
| 84 | + /// @param chainId The chain to register on; see `WETH9_*_CHAIN_ID`. | |
| 71 | 85 | /// @return registry The registry, with `wethAt` registered. |
| 72 | 86 | /// @return weth The wETH now backing any `Love` deployed here. |
| 73 | - function setUpChain(address wethAt, string memory fixture) internal returns (WETHRegistry registry, IWETH weth) { | |
| 87 | + function setUpChain(address wethAt, string memory fixture, uint256 chainId) | |
| 88 | + internal | |
| 89 | + returns (WETHRegistry registry, IWETH weth) | |
| 90 | + { | |
| 91 | + vm.chainId(chainId); | |
| 74 | 92 | registry = deployRegistry(); |
| 75 | 93 | weth = etchWeth(wethAt, fixture); |
| 76 | 94 | register(registry, weth); |
| @@ -22,9 +22,17 @@ abstract contract Fixtures is CommonBase, StdCheats { | |||
| 22 | /// @notice OP Stack legacy WETH9, as deployed on Base, Mode and Zora. | 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"; | 23 | string internal constant WETH9_OP_LEGACY = "test/fixtures/weth9-op-legacy.hex"; |
| 24 | 24 | ||
| 25 | + /// @notice A chain WETH9_OP_LEGACY's codehash is reviewed for. | ||
| 26 | + /// @dev Base; Mode and Zora share the same codehash and would do equally | ||
| 27 | + /// well here, since `isKnownImplementation` accepts all three. | ||
| 28 | + uint256 internal constant WETH9_OP_LEGACY_CHAIN_ID = 8453; | ||
| 29 | + | ||
| 25 | /// @notice Canonical WETH9, as deployed on Ethereum mainnet. | 30 | /// @notice Canonical WETH9, as deployed on Ethereum mainnet. |
| 26 | string internal constant WETH9_CANONICAL = "test/fixtures/weth9-canonical.hex"; | 31 | string internal constant WETH9_CANONICAL = "test/fixtures/weth9-canonical.hex"; |
| 27 | 32 | ||
| 33 | + /// @notice The chain WETH9_CANONICAL's codehash is reviewed for. | ||
| 34 | + uint256 internal constant WETH9_CANONICAL_CHAIN_ID = 1; | ||
| 35 | + | ||
| 28 | /// @notice Put real wETH runtime bytecode at `where`. | 36 | /// @notice Put real wETH runtime bytecode at `where`. |
| 29 | /// @dev Reads the fixture rather than embedding it, so refreshing a | 37 | /// @dev Reads the fixture rather than embedding it, so refreshing a |
| 30 | /// fixture from chain state does not mean editing Solidity. | 38 | /// fixture from chain state does not mean editing Solidity. |
| @@ -66,11 +74,21 @@ abstract contract Fixtures is CommonBase, StdCheats { | |||
| 66 | } | 74 | } |
| 67 | 75 | ||
| 68 | /// @notice The whole chain in one call: registry, wETH, registration. | 76 | /// @notice The whole chain in one call: registry, wETH, registration. |
| 77 | + /// @dev Sets `block.chainid` to `chainId` first — the registry now checks | ||
| 78 | + /// the candidate's codehash against the chain it's being registered | ||
| 79 | + /// on, not just against the allowlist in isolation, so a test has to | ||
| 80 | + /// put itself on a chain the fixture's codehash is actually reviewed | ||
| 81 | + /// for. Use one of the `WETH9_*_CHAIN_ID` constants above. | ||
| 69 | /// @param wethAt The address to etch wETH at. | 82 | /// @param wethAt The address to etch wETH at. |
| 70 | /// @param fixture Which implementation to etch. | 83 | /// @param fixture Which implementation to etch. |
| 84 | + /// @param chainId The chain to register on; see `WETH9_*_CHAIN_ID`. | ||
| 71 | /// @return registry The registry, with `wethAt` registered. | 85 | /// @return registry The registry, with `wethAt` registered. |
| 72 | /// @return weth The wETH now backing any `Love` deployed here. | 86 | /// @return weth The wETH now backing any `Love` deployed here. |
| 73 | - function setUpChain(address wethAt, string memory fixture) internal returns (WETHRegistry registry, IWETH weth) { | 87 | + function setUpChain(address wethAt, string memory fixture, uint256 chainId) |
| 88 | + internal | ||
| 89 | + returns (WETHRegistry registry, IWETH weth) | ||
| 90 | + { | ||
| 91 | + vm.chainId(chainId); | ||
| 74 | registry = deployRegistry(); | 92 | registry = deployRegistry(); |
| 75 | weth = etchWeth(wethAt, fixture); | 93 | weth = etchWeth(wethAt, fixture); |
| 76 | register(registry, weth); | 94 | register(registry, weth); |
modified
test/Love.t.sol +1 -1 | @@ -29,7 +29,7 @@ contract LoveTest is Test, Fixtures { | ||
| 29 | 29 | address constant WETH_AT = 0x4200000000000000000000000000000000000006; |
| 30 | 30 | |
| 31 | 31 | function setUp() public { |
| 32 | - (, weth) = setUpChain(WETH_AT, WETH9_OP_LEGACY); | |
| 32 | + (, weth) = setUpChain(WETH_AT, WETH9_OP_LEGACY, WETH9_OP_LEGACY_CHAIN_ID); | |
| 33 | 33 | love = new Love(); |
| 34 | 34 | |
| 35 | 35 | _fund(alice, 10 ether); |
| @@ -29,7 +29,7 @@ contract LoveTest is Test, Fixtures { | |||
| 29 | address constant WETH_AT = 0x4200000000000000000000000000000000000006; | 29 | address constant WETH_AT = 0x4200000000000000000000000000000000000006; |
| 30 | 30 | ||
| 31 | function setUp() public { | 31 | function setUp() public { |
| 32 | - (, weth) = setUpChain(WETH_AT, WETH9_OP_LEGACY); | 32 | + (, weth) = setUpChain(WETH_AT, WETH9_OP_LEGACY, WETH9_OP_LEGACY_CHAIN_ID); |
| 33 | love = new Love(); | 33 | love = new Love(); |
| 34 | 34 | ||
| 35 | _fund(alice, 10 ether); | 35 | _fund(alice, 10 ether); |
modified
test/LoveCreate2.t.sol +7 -8 | @@ -36,7 +36,7 @@ contract LoveCreate2Test is Test, Fixtures { | ||
| 36 | 36 | /// change of optimizer settings — this fails rather than shipping a |
| 37 | 37 | /// token pointing at an empty address. |
| 38 | 38 | function test_LoveRegistryConstantMatchesDeterministicAddress() public { |
| 39 | - setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY); | |
| 39 | + setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY, WETH9_OP_LEGACY_CHAIN_ID); | |
| 40 | 40 | |
| 41 | 41 | assertEq(new Love().REGISTRY(), script.registryAddress()); |
| 42 | 42 | } |
| @@ -62,7 +62,7 @@ contract LoveCreate2Test is Test, Fixtures { | ||
| 62 | 62 | //////////////////////////////////////////////////////////////*/ |
| 63 | 63 | |
| 64 | 64 | function test_DeploysAtPredictedAddress() public { |
| 65 | - setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY); | |
| 65 | + setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY, WETH9_OP_LEGACY_CHAIN_ID); | |
| 66 | 66 | |
| 67 | 67 | address predicted = vm.computeCreate2Address(SALT, keccak256(script.initCode()), CREATE2_FACTORY); |
| 68 | 68 | Love deployed = Love(_deploy(SALT)); |
| @@ -74,7 +74,7 @@ contract LoveCreate2Test is Test, Fixtures { | ||
| 74 | 74 | |
| 75 | 75 | /// @dev What the deploy script prints must be what the chain gives back. |
| 76 | 76 | function test_ScriptPredictionMatchesDeployment() public { |
| 77 | - setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY); | |
| 77 | + setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY, WETH9_OP_LEGACY_CHAIN_ID); | |
| 78 | 78 | |
| 79 | 79 | assertEq(script.predict(SALT), _deploy(SALT)); |
| 80 | 80 | } |
| @@ -116,7 +116,7 @@ contract LoveCreate2Test is Test, Fixtures { | ||
| 116 | 116 | // to build over that, so the registry could never be redeployed. |
| 117 | 117 | uint256 freshChain = vm.snapshotState(); |
| 118 | 118 | |
| 119 | - setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY); | |
| 119 | + setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY, WETH9_OP_LEGACY_CHAIN_ID); | |
| 120 | 120 | address onOpStack = _deploy(SALT); |
| 121 | 121 | assertEq(address(Love(onOpStack).WETH()), OP_STACK_WETH); |
| 122 | 122 | |
| @@ -124,8 +124,7 @@ contract LoveCreate2Test is Test, Fixtures { | ||
| 124 | 124 | |
| 125 | 125 | // Different chain id, a different wETH implementation, at a different |
| 126 | 126 | // address. |
| 127 | - vm.chainId(1); | |
| 128 | - setUpChain(MAINNET_WETH, WETH9_CANONICAL); | |
| 127 | + setUpChain(MAINNET_WETH, WETH9_CANONICAL, WETH9_CANONICAL_CHAIN_ID); | |
| 129 | 128 | address onMainnet = _deploy(SALT); |
| 130 | 129 | assertEq(address(Love(onMainnet).WETH()), MAINNET_WETH); |
| 131 | 130 | |
| @@ -135,7 +134,7 @@ contract LoveCreate2Test is Test, Fixtures { | ||
| 135 | 134 | /// @dev Redeploying with the same salt must fail, not silently return the |
| 136 | 135 | /// existing token. |
| 137 | 136 | function test_RevertWhen_RedeployingWithSameSalt() public { |
| 138 | - setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY); | |
| 137 | + setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY, WETH9_OP_LEGACY_CHAIN_ID); | |
| 139 | 138 | _deploy(SALT); |
| 140 | 139 | |
| 141 | 140 | (bool ok,) = CREATE2_FACTORY.call(abi.encodePacked(SALT, script.initCode())); |
| @@ -143,7 +142,7 @@ contract LoveCreate2Test is Test, Fixtures { | ||
| 143 | 142 | } |
| 144 | 143 | |
| 145 | 144 | function testFuzz_PredictionMatchesDeployment(bytes32 salt) public { |
| 146 | - setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY); | |
| 145 | + setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY, WETH9_OP_LEGACY_CHAIN_ID); | |
| 147 | 146 | |
| 148 | 147 | assertEq(script.predict(salt), _deploy(salt)); |
| 149 | 148 | } |
| @@ -36,7 +36,7 @@ contract LoveCreate2Test is Test, Fixtures { | |||
| 36 | /// change of optimizer settings — this fails rather than shipping a | 36 | /// change of optimizer settings — this fails rather than shipping a |
| 37 | /// token pointing at an empty address. | 37 | /// token pointing at an empty address. |
| 38 | function test_LoveRegistryConstantMatchesDeterministicAddress() public { | 38 | function test_LoveRegistryConstantMatchesDeterministicAddress() public { |
| 39 | - setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY); | 39 | + setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY, WETH9_OP_LEGACY_CHAIN_ID); |
| 40 | 40 | ||
| 41 | assertEq(new Love().REGISTRY(), script.registryAddress()); | 41 | assertEq(new Love().REGISTRY(), script.registryAddress()); |
| 42 | } | 42 | } |
| @@ -62,7 +62,7 @@ contract LoveCreate2Test is Test, Fixtures { | |||
| 62 | //////////////////////////////////////////////////////////////*/ | 62 | //////////////////////////////////////////////////////////////*/ |
| 63 | 63 | ||
| 64 | function test_DeploysAtPredictedAddress() public { | 64 | function test_DeploysAtPredictedAddress() public { |
| 65 | - setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY); | 65 | + setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY, WETH9_OP_LEGACY_CHAIN_ID); |
| 66 | 66 | ||
| 67 | address predicted = vm.computeCreate2Address(SALT, keccak256(script.initCode()), CREATE2_FACTORY); | 67 | address predicted = vm.computeCreate2Address(SALT, keccak256(script.initCode()), CREATE2_FACTORY); |
| 68 | Love deployed = Love(_deploy(SALT)); | 68 | Love deployed = Love(_deploy(SALT)); |
| @@ -74,7 +74,7 @@ contract LoveCreate2Test is Test, Fixtures { | |||
| 74 | 74 | ||
| 75 | /// @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. |
| 76 | function test_ScriptPredictionMatchesDeployment() public { | 76 | function test_ScriptPredictionMatchesDeployment() public { |
| 77 | - setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY); | 77 | + setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY, WETH9_OP_LEGACY_CHAIN_ID); |
| 78 | 78 | ||
| 79 | assertEq(script.predict(SALT), _deploy(SALT)); | 79 | assertEq(script.predict(SALT), _deploy(SALT)); |
| 80 | } | 80 | } |
| @@ -116,7 +116,7 @@ contract LoveCreate2Test is Test, Fixtures { | |||
| 116 | // to build over that, so the registry could never be redeployed. | 116 | // to build over that, so the registry could never be redeployed. |
| 117 | uint256 freshChain = vm.snapshotState(); | 117 | uint256 freshChain = vm.snapshotState(); |
| 118 | 118 | ||
| 119 | - setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY); | 119 | + setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY, WETH9_OP_LEGACY_CHAIN_ID); |
| 120 | address onOpStack = _deploy(SALT); | 120 | address onOpStack = _deploy(SALT); |
| 121 | assertEq(address(Love(onOpStack).WETH()), OP_STACK_WETH); | 121 | assertEq(address(Love(onOpStack).WETH()), OP_STACK_WETH); |
| 122 | 122 | ||
| @@ -124,8 +124,7 @@ contract LoveCreate2Test is Test, Fixtures { | |||
| 124 | 124 | ||
| 125 | // Different chain id, a different wETH implementation, at a different | 125 | // Different chain id, a different wETH implementation, at a different |
| 126 | // address. | 126 | // address. |
| 127 | - vm.chainId(1); | 127 | + setUpChain(MAINNET_WETH, WETH9_CANONICAL, WETH9_CANONICAL_CHAIN_ID); |
| 128 | - setUpChain(MAINNET_WETH, WETH9_CANONICAL); | ||
| 129 | address onMainnet = _deploy(SALT); | 128 | address onMainnet = _deploy(SALT); |
| 130 | assertEq(address(Love(onMainnet).WETH()), MAINNET_WETH); | 129 | assertEq(address(Love(onMainnet).WETH()), MAINNET_WETH); |
| 131 | 130 | ||
| @@ -135,7 +134,7 @@ contract LoveCreate2Test is Test, Fixtures { | |||
| 135 | /// @dev Redeploying with the same salt must fail, not silently return the | 134 | /// @dev Redeploying with the same salt must fail, not silently return the |
| 136 | /// existing token. | 135 | /// existing token. |
| 137 | function test_RevertWhen_RedeployingWithSameSalt() public { | 136 | function test_RevertWhen_RedeployingWithSameSalt() public { |
| 138 | - setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY); | 137 | + setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY, WETH9_OP_LEGACY_CHAIN_ID); |
| 139 | _deploy(SALT); | 138 | _deploy(SALT); |
| 140 | 139 | ||
| 141 | (bool ok,) = CREATE2_FACTORY.call(abi.encodePacked(SALT, script.initCode())); | 140 | (bool ok,) = CREATE2_FACTORY.call(abi.encodePacked(SALT, script.initCode())); |
| @@ -143,7 +142,7 @@ contract LoveCreate2Test is Test, Fixtures { | |||
| 143 | } | 142 | } |
| 144 | 143 | ||
| 145 | function testFuzz_PredictionMatchesDeployment(bytes32 salt) public { | 144 | function testFuzz_PredictionMatchesDeployment(bytes32 salt) public { |
| 146 | - setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY); | 145 | + setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY, WETH9_OP_LEGACY_CHAIN_ID); |
| 147 | 146 | ||
| 148 | assertEq(script.predict(salt), _deploy(salt)); | 147 | assertEq(script.predict(salt), _deploy(salt)); |
| 149 | } | 148 | } |
modified
test/LoveEnvelope.t.sol +1 -1 | @@ -29,7 +29,7 @@ contract LoveEnvelopeTest is Test, Fixtures { | ||
| 29 | 29 | address constant WETH_AT = 0x4200000000000000000000000000000000000006; |
| 30 | 30 | |
| 31 | 31 | function setUp() public { |
| 32 | - (, weth) = setUpChain(WETH_AT, WETH9_OP_LEGACY); | |
| 32 | + (, weth) = setUpChain(WETH_AT, WETH9_OP_LEGACY, WETH9_OP_LEGACY_CHAIN_ID); | |
| 33 | 33 | love = new Love(); |
| 34 | 34 | |
| 35 | 35 | _fund(alice, 10 ether); |
| @@ -29,7 +29,7 @@ contract LoveEnvelopeTest is Test, Fixtures { | |||
| 29 | address constant WETH_AT = 0x4200000000000000000000000000000000000006; | 29 | address constant WETH_AT = 0x4200000000000000000000000000000000000006; |
| 30 | 30 | ||
| 31 | function setUp() public { | 31 | function setUp() public { |
| 32 | - (, weth) = setUpChain(WETH_AT, WETH9_OP_LEGACY); | 32 | + (, weth) = setUpChain(WETH_AT, WETH9_OP_LEGACY, WETH9_OP_LEGACY_CHAIN_ID); |
| 33 | love = new Love(); | 33 | love = new Love(); |
| 34 | 34 | ||
| 35 | _fund(alice, 10 ether); | 35 | _fund(alice, 10 ether); |
modified
test/WETHRegistry.t.sol +110 -32 | @@ -33,6 +33,9 @@ contract WETHRegistryTest is Test, Fixtures { | ||
| 33 | 33 | event Registered(IWETH indexed weth, address indexed registrar, bytes32 codeHash); |
| 34 | 34 | |
| 35 | 35 | function setUp() public { |
| 36 | + // Ambient default: every test below registers WETH9_OP_LEGACY unless | |
| 37 | + // it says otherwise, so put the chain where that codehash is reviewed. | |
| 38 | + vm.chainId(WETH9_OP_LEGACY_CHAIN_ID); | |
| 36 | 39 | registry = deployRegistry(); |
| 37 | 40 | probe = registry.PROBE(); |
| 38 | 41 | } |
| @@ -41,42 +44,109 @@ contract WETHRegistryTest is Test, Fixtures { | ||
| 41 | 44 | THE ALLOWLIST |
| 42 | 45 | //////////////////////////////////////////////////////////////*/ |
| 43 | 46 | |
| 44 | - /// @dev The ten entries, reproducible from chain state with | |
| 45 | - /// `script/weth-codehashes.sh`. Listing them here means a hash edited | |
| 46 | - /// in the contract shows up as a failing test rather than as a silent | |
| 47 | - /// change to what LOVE will accept. | |
| 47 | + /// @dev The ten hashes, each paired with every chain ID it's actually | |
| 48 | + /// reviewed for, reproducible from chain state with | |
| 49 | + /// `script/weth-codehashes.sh`. Listing them here means a hash or a | |
| 50 | + /// chain ID edited in the contract shows up as a failing test rather | |
| 51 | + /// than as a silent change to what LOVE will accept. | |
| 48 | 52 | function test_KnownImplementations() public view { |
| 49 | - bytes32[10] memory allowed = [ | |
| 50 | - // OP Stack legacy WETH9 | |
| 51 | - bytes32(0x779bbf2a738ef09d961c945116197e2ac764c1b39304b2b4418cd4e42668b173), | |
| 52 | - 0x8a3a1f6a9f9dce633117adee5b458245835a8645a8c8726a26382a4622508b1c, | |
| 53 | - 0x557c8e14d33f7cd67cad0141e1a49ebf3488a447fc3df7aa66b127778a0383d1, | |
| 54 | - 0xf35fe602ba2a3b96f2e27ff7c8b8010800a8d0d616a5fb1f902e087b590355f3, | |
| 55 | - // Canonical WETH9 | |
| 56 | - 0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23, | |
| 57 | - 0xa670ec6c272ddec6d328d6f3d5cad65a841a6ab45e8e5cf825150eb458be4f1f, | |
| 58 | - 0x032e9cab14331328530468e54f1b91777b4d5c9dbbb400884badb32bc4113585, | |
| 59 | - // OP Stack WETH, solc 0.8.15 | |
| 60 | - 0xd0f1614c5dacfbd34f1c6f500f397009e4c9a8bfd4e02db353edb2253d9a8012, | |
| 61 | - // Taiko | |
| 62 | - 0x9f3d95086909fce850d997158aba31abe26c3aad6a413107ca0bf9d53a7c42e9, | |
| 63 | - // Scroll | |
| 64 | - 0xe8c4073351c26b9831c1e5af153b9be4713a4af9edfdf32b58077b735e120f14 | |
| 65 | - ]; | |
| 66 | - | |
| 67 | - for (uint256 i = 0; i < allowed.length; i++) { | |
| 68 | - assertTrue(registry.isKnownImplementation(allowed[i]), "allowlisted hash rejected"); | |
| 69 | - } | |
| 53 | + assertTrue( | |
| 54 | + registry.isKnownImplementation(0x779bbf2a738ef09d961c945116197e2ac764c1b39304b2b4418cd4e42668b173, 10), | |
| 55 | + "optimism" | |
| 56 | + ); | |
| 57 | + assertTrue( | |
| 58 | + registry.isKnownImplementation(0x8a3a1f6a9f9dce633117adee5b458245835a8645a8c8726a26382a4622508b1c, 8453), | |
| 59 | + "base" | |
| 60 | + ); | |
| 61 | + assertTrue( | |
| 62 | + registry.isKnownImplementation(0x8a3a1f6a9f9dce633117adee5b458245835a8645a8c8726a26382a4622508b1c, 34_443), | |
| 63 | + "mode" | |
| 64 | + ); | |
| 65 | + assertTrue( | |
| 66 | + registry.isKnownImplementation( | |
| 67 | + 0x8a3a1f6a9f9dce633117adee5b458245835a8645a8c8726a26382a4622508b1c, 7_777_777 | |
| 68 | + ), | |
| 69 | + "zora" | |
| 70 | + ); | |
| 71 | + assertTrue( | |
| 72 | + registry.isKnownImplementation(0x557c8e14d33f7cd67cad0141e1a49ebf3488a447fc3df7aa66b127778a0383d1, 480), | |
| 73 | + "world-chain" | |
| 74 | + ); | |
| 75 | + assertTrue( | |
| 76 | + registry.isKnownImplementation(0xf35fe602ba2a3b96f2e27ff7c8b8010800a8d0d616a5fb1f902e087b590355f3, 1135), | |
| 77 | + "lisk" | |
| 78 | + ); | |
| 79 | + assertTrue( | |
| 80 | + registry.isKnownImplementation(0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23, 1), | |
| 81 | + "ethereum" | |
| 82 | + ); | |
| 83 | + assertTrue( | |
| 84 | + registry.isKnownImplementation(0xa670ec6c272ddec6d328d6f3d5cad65a841a6ab45e8e5cf825150eb458be4f1f, 59_144), | |
| 85 | + "linea" | |
| 86 | + ); | |
| 87 | + assertTrue( | |
| 88 | + registry.isKnownImplementation(0x032e9cab14331328530468e54f1b91777b4d5c9dbbb400884badb32bc4113585, 1101), | |
| 89 | + "polygon-zkevm" | |
| 90 | + ); | |
| 91 | + assertTrue( | |
| 92 | + registry.isKnownImplementation(0xd0f1614c5dacfbd34f1c6f500f397009e4c9a8bfd4e02db353edb2253d9a8012, 130), | |
| 93 | + "unichain" | |
| 94 | + ); | |
| 95 | + assertTrue( | |
| 96 | + registry.isKnownImplementation(0xd0f1614c5dacfbd34f1c6f500f397009e4c9a8bfd4e02db353edb2253d9a8012, 1868), | |
| 97 | + "soneium" | |
| 98 | + ); | |
| 99 | + assertTrue( | |
| 100 | + registry.isKnownImplementation(0xd0f1614c5dacfbd34f1c6f500f397009e4c9a8bfd4e02db353edb2253d9a8012, 57_073), | |
| 101 | + "ink" | |
| 102 | + ); | |
| 103 | + assertTrue( | |
| 104 | + registry.isKnownImplementation(0x9f3d95086909fce850d997158aba31abe26c3aad6a413107ca0bf9d53a7c42e9, 167_000), | |
| 105 | + "taiko" | |
| 106 | + ); | |
| 107 | + assertTrue( | |
| 108 | + registry.isKnownImplementation(0xe8c4073351c26b9831c1e5af153b9be4713a4af9edfdf32b58077b735e120f14, 534_352), | |
| 109 | + "scroll" | |
| 110 | + ); | |
| 70 | 111 | } |
| 71 | 112 | |
| 72 | 113 | function test_EmptyCodeHashIsNotKnown() public view { |
| 73 | - assertFalse(registry.isKnownImplementation(bytes32(0))); | |
| 74 | - assertFalse(registry.isKnownImplementation(keccak256(""))); | |
| 114 | + assertFalse(registry.isKnownImplementation(bytes32(0), WETH9_OP_LEGACY_CHAIN_ID)); | |
| 115 | + assertFalse(registry.isKnownImplementation(keccak256(""), WETH9_OP_LEGACY_CHAIN_ID)); | |
| 116 | + } | |
| 117 | + | |
| 118 | + function testFuzz_UnknownHashesAreRejected(bytes32 codeHash, uint256 chainId) public view { | |
| 119 | + vm.assume(!registry.isKnownImplementation(codeHash, chainId)); | |
| 120 | + assertFalse(registry.isKnownImplementation(codeHash, chainId)); | |
| 75 | 121 | } |
| 76 | 122 | |
| 77 | - function testFuzz_UnknownHashesAreRejected(bytes32 codeHash) public view { | |
| 78 | - vm.assume(!registry.isKnownImplementation(codeHash)); | |
| 79 | - assertFalse(registry.isKnownImplementation(codeHash)); | |
| 123 | + /// @dev The gap this whole change closes: canonical WETH9's bytecode | |
| 124 | + /// carries no dependency on which chain it runs on, so a codehash | |
| 125 | + /// match by itself would say nothing about whether the native | |
| 126 | + /// currency behind it is actually ETH. 137 is Polygon PoS, whose | |
| 127 | + /// native token is POL — canonical WETH9's hash was never reviewed | |
| 128 | + /// there, so it must not pass here even though it passes on chain 1. | |
| 129 | + function test_RevertWhen_ReviewedHashOnWrongChain() public view { | |
| 130 | + bytes32 canonicalHash = 0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23; | |
| 131 | + assertTrue(registry.isKnownImplementation(canonicalHash, 1), "sanity: valid on ethereum"); | |
| 132 | + assertFalse(registry.isKnownImplementation(canonicalHash, 137), "must not validate on polygon pos"); | |
| 133 | + } | |
| 134 | + | |
| 135 | + /// @dev End-to-end version of the above: the exact same candidate | |
| 136 | + /// contract, byte-identical bytecode, registers on the chain its | |
| 137 | + /// codehash was reviewed for and is rejected on one it wasn't — | |
| 138 | + /// Native Currency Substitution (docs/notes/native-currency-substitution.md). | |
| 139 | + function test_RevertWhen_RegisteringCanonicalWethOnWrongChain() public { | |
| 140 | + IWETH weth = etchWeth(OTHER_WETH_AT, WETH9_CANONICAL); | |
| 141 | + deal(address(this), 1 ether); | |
| 142 | + | |
| 143 | + vm.chainId(137); // polygon pos, native token POL, never reviewed for this hash | |
| 144 | + vm.expectRevert( | |
| 145 | + abi.encodeWithSelector( | |
| 146 | + WETHRegistry.UnknownImplementation.selector, IWETH(OTHER_WETH_AT), OTHER_WETH_AT.codehash, uint256(137) | |
| 147 | + ) | |
| 148 | + ); | |
| 149 | + registry.register{value: probe}(weth); | |
| 80 | 150 | } |
| 81 | 151 | |
| 82 | 152 | /*////////////////////////////////////////////////////////////// |
| @@ -91,6 +161,7 @@ contract WETHRegistryTest is Test, Fixtures { | ||
| 91 | 161 | } |
| 92 | 162 | |
| 93 | 163 | function test_RegistersCanonicalWeth() public { |
| 164 | + vm.chainId(WETH9_CANONICAL_CHAIN_ID); | |
| 94 | 165 | IWETH weth = etchWeth(OTHER_WETH_AT, WETH9_CANONICAL); |
| 95 | 166 | register(registry, weth); |
| 96 | 167 | |
| @@ -144,7 +215,10 @@ contract WETHRegistryTest is Test, Fixtures { | ||
| 144 | 215 | |
| 145 | 216 | vm.expectRevert( |
| 146 | 217 | abi.encodeWithSelector( |
| 147 | - WETHRegistry.UnknownImplementation.selector, IWETH(address(mock)), address(mock).codehash | |
| 218 | + WETHRegistry.UnknownImplementation.selector, | |
| 219 | + IWETH(address(mock)), | |
| 220 | + address(mock).codehash, | |
| 221 | + WETH9_OP_LEGACY_CHAIN_ID | |
| 148 | 222 | ) |
| 149 | 223 | ); |
| 150 | 224 | registry.register{value: probe}(IWETH(address(mock))); |
| @@ -154,7 +228,11 @@ contract WETHRegistryTest is Test, Fixtures { | ||
| 154 | 228 | address empty = makeAddr("empty"); |
| 155 | 229 | deal(address(this), 1 ether); |
| 156 | 230 | |
| 157 | - vm.expectRevert(abi.encodeWithSelector(WETHRegistry.UnknownImplementation.selector, IWETH(empty), bytes32(0))); | |
| 231 | + vm.expectRevert( | |
| 232 | + abi.encodeWithSelector( | |
| 233 | + WETHRegistry.UnknownImplementation.selector, IWETH(empty), bytes32(0), WETH9_OP_LEGACY_CHAIN_ID | |
| 234 | + ) | |
| 235 | + ); | |
| 158 | 236 | registry.register{value: probe}(IWETH(empty)); |
| 159 | 237 | } |
| 160 | 238 | |
| @@ -33,6 +33,9 @@ contract WETHRegistryTest is Test, Fixtures { | |||
| 33 | event Registered(IWETH indexed weth, address indexed registrar, bytes32 codeHash); | 33 | event Registered(IWETH indexed weth, address indexed registrar, bytes32 codeHash); |
| 34 | 34 | ||
| 35 | function setUp() public { | 35 | function setUp() public { |
| 36 | + // Ambient default: every test below registers WETH9_OP_LEGACY unless | ||
| 37 | + // it says otherwise, so put the chain where that codehash is reviewed. | ||
| 38 | + vm.chainId(WETH9_OP_LEGACY_CHAIN_ID); | ||
| 36 | registry = deployRegistry(); | 39 | registry = deployRegistry(); |
| 37 | probe = registry.PROBE(); | 40 | probe = registry.PROBE(); |
| 38 | } | 41 | } |
| @@ -41,42 +44,109 @@ contract WETHRegistryTest is Test, Fixtures { | |||
| 41 | THE ALLOWLIST | 44 | THE ALLOWLIST |
| 42 | //////////////////////////////////////////////////////////////*/ | 45 | //////////////////////////////////////////////////////////////*/ |
| 43 | 46 | ||
| 44 | - /// @dev The ten entries, reproducible from chain state with | 47 | + /// @dev The ten hashes, each paired with every chain ID it's actually |
| 45 | - /// `script/weth-codehashes.sh`. Listing them here means a hash edited | 48 | + /// reviewed for, reproducible from chain state with |
| 46 | - /// in the contract shows up as a failing test rather than as a silent | 49 | + /// `script/weth-codehashes.sh`. Listing them here means a hash or a |
| 47 | - /// change to what LOVE will accept. | 50 | + /// chain ID edited in the contract shows up as a failing test rather |
| 51 | + /// than as a silent change to what LOVE will accept. | ||
| 48 | function test_KnownImplementations() public view { | 52 | function test_KnownImplementations() public view { |
| 49 | - bytes32[10] memory allowed = [ | 53 | + assertTrue( |
| 50 | - // OP Stack legacy WETH9 | 54 | + registry.isKnownImplementation(0x779bbf2a738ef09d961c945116197e2ac764c1b39304b2b4418cd4e42668b173, 10), |
| 51 | - bytes32(0x779bbf2a738ef09d961c945116197e2ac764c1b39304b2b4418cd4e42668b173), | 55 | + "optimism" |
| 52 | - 0x8a3a1f6a9f9dce633117adee5b458245835a8645a8c8726a26382a4622508b1c, | 56 | + ); |
| 53 | - 0x557c8e14d33f7cd67cad0141e1a49ebf3488a447fc3df7aa66b127778a0383d1, | 57 | + assertTrue( |
| 54 | - 0xf35fe602ba2a3b96f2e27ff7c8b8010800a8d0d616a5fb1f902e087b590355f3, | 58 | + registry.isKnownImplementation(0x8a3a1f6a9f9dce633117adee5b458245835a8645a8c8726a26382a4622508b1c, 8453), |
| 55 | - // Canonical WETH9 | 59 | + "base" |
| 56 | - 0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23, | 60 | + ); |
| 57 | - 0xa670ec6c272ddec6d328d6f3d5cad65a841a6ab45e8e5cf825150eb458be4f1f, | 61 | + assertTrue( |
| 58 | - 0x032e9cab14331328530468e54f1b91777b4d5c9dbbb400884badb32bc4113585, | 62 | + registry.isKnownImplementation(0x8a3a1f6a9f9dce633117adee5b458245835a8645a8c8726a26382a4622508b1c, 34_443), |
| 59 | - // OP Stack WETH, solc 0.8.15 | 63 | + "mode" |
| 60 | - 0xd0f1614c5dacfbd34f1c6f500f397009e4c9a8bfd4e02db353edb2253d9a8012, | 64 | + ); |
| 61 | - // Taiko | 65 | + assertTrue( |
| 62 | - 0x9f3d95086909fce850d997158aba31abe26c3aad6a413107ca0bf9d53a7c42e9, | 66 | + registry.isKnownImplementation( |
| 63 | - // Scroll | 67 | + 0x8a3a1f6a9f9dce633117adee5b458245835a8645a8c8726a26382a4622508b1c, 7_777_777 |
| 64 | - 0xe8c4073351c26b9831c1e5af153b9be4713a4af9edfdf32b58077b735e120f14 | 68 | + ), |
| 65 | - ]; | 69 | + "zora" |
| 66 | - | 70 | + ); |
| 67 | - for (uint256 i = 0; i < allowed.length; i++) { | 71 | + assertTrue( |
| 68 | - assertTrue(registry.isKnownImplementation(allowed[i]), "allowlisted hash rejected"); | 72 | + registry.isKnownImplementation(0x557c8e14d33f7cd67cad0141e1a49ebf3488a447fc3df7aa66b127778a0383d1, 480), |
| 69 | - } | 73 | + "world-chain" |
| 74 | + ); | ||
| 75 | + assertTrue( | ||
| 76 | + registry.isKnownImplementation(0xf35fe602ba2a3b96f2e27ff7c8b8010800a8d0d616a5fb1f902e087b590355f3, 1135), | ||
| 77 | + "lisk" | ||
| 78 | + ); | ||
| 79 | + assertTrue( | ||
| 80 | + registry.isKnownImplementation(0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23, 1), | ||
| 81 | + "ethereum" | ||
| 82 | + ); | ||
| 83 | + assertTrue( | ||
| 84 | + registry.isKnownImplementation(0xa670ec6c272ddec6d328d6f3d5cad65a841a6ab45e8e5cf825150eb458be4f1f, 59_144), | ||
| 85 | + "linea" | ||
| 86 | + ); | ||
| 87 | + assertTrue( | ||
| 88 | + registry.isKnownImplementation(0x032e9cab14331328530468e54f1b91777b4d5c9dbbb400884badb32bc4113585, 1101), | ||
| 89 | + "polygon-zkevm" | ||
| 90 | + ); | ||
| 91 | + assertTrue( | ||
| 92 | + registry.isKnownImplementation(0xd0f1614c5dacfbd34f1c6f500f397009e4c9a8bfd4e02db353edb2253d9a8012, 130), | ||
| 93 | + "unichain" | ||
| 94 | + ); | ||
| 95 | + assertTrue( | ||
| 96 | + registry.isKnownImplementation(0xd0f1614c5dacfbd34f1c6f500f397009e4c9a8bfd4e02db353edb2253d9a8012, 1868), | ||
| 97 | + "soneium" | ||
| 98 | + ); | ||
| 99 | + assertTrue( | ||
| 100 | + registry.isKnownImplementation(0xd0f1614c5dacfbd34f1c6f500f397009e4c9a8bfd4e02db353edb2253d9a8012, 57_073), | ||
| 101 | + "ink" | ||
| 102 | + ); | ||
| 103 | + assertTrue( | ||
| 104 | + registry.isKnownImplementation(0x9f3d95086909fce850d997158aba31abe26c3aad6a413107ca0bf9d53a7c42e9, 167_000), | ||
| 105 | + "taiko" | ||
| 106 | + ); | ||
| 107 | + assertTrue( | ||
| 108 | + registry.isKnownImplementation(0xe8c4073351c26b9831c1e5af153b9be4713a4af9edfdf32b58077b735e120f14, 534_352), | ||
| 109 | + "scroll" | ||
| 110 | + ); | ||
| 70 | } | 111 | } |
| 71 | 112 | ||
| 72 | function test_EmptyCodeHashIsNotKnown() public view { | 113 | function test_EmptyCodeHashIsNotKnown() public view { |
| 73 | - assertFalse(registry.isKnownImplementation(bytes32(0))); | 114 | + assertFalse(registry.isKnownImplementation(bytes32(0), WETH9_OP_LEGACY_CHAIN_ID)); |
| 74 | - assertFalse(registry.isKnownImplementation(keccak256(""))); | 115 | + assertFalse(registry.isKnownImplementation(keccak256(""), WETH9_OP_LEGACY_CHAIN_ID)); |
| 116 | + } | ||
| 117 | + | ||
| 118 | + function testFuzz_UnknownHashesAreRejected(bytes32 codeHash, uint256 chainId) public view { | ||
| 119 | + vm.assume(!registry.isKnownImplementation(codeHash, chainId)); | ||
| 120 | + assertFalse(registry.isKnownImplementation(codeHash, chainId)); | ||
| 75 | } | 121 | } |
| 76 | 122 | ||
| 77 | - function testFuzz_UnknownHashesAreRejected(bytes32 codeHash) public view { | 123 | + /// @dev The gap this whole change closes: canonical WETH9's bytecode |
| 78 | - vm.assume(!registry.isKnownImplementation(codeHash)); | 124 | + /// carries no dependency on which chain it runs on, so a codehash |
| 79 | - assertFalse(registry.isKnownImplementation(codeHash)); | 125 | + /// match by itself would say nothing about whether the native |
| 126 | + /// currency behind it is actually ETH. 137 is Polygon PoS, whose | ||
| 127 | + /// native token is POL — canonical WETH9's hash was never reviewed | ||
| 128 | + /// there, so it must not pass here even though it passes on chain 1. | ||
| 129 | + function test_RevertWhen_ReviewedHashOnWrongChain() public view { | ||
| 130 | + bytes32 canonicalHash = 0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23; | ||
| 131 | + assertTrue(registry.isKnownImplementation(canonicalHash, 1), "sanity: valid on ethereum"); | ||
| 132 | + assertFalse(registry.isKnownImplementation(canonicalHash, 137), "must not validate on polygon pos"); | ||
| 133 | + } | ||
| 134 | + | ||
| 135 | + /// @dev End-to-end version of the above: the exact same candidate | ||
| 136 | + /// contract, byte-identical bytecode, registers on the chain its | ||
| 137 | + /// codehash was reviewed for and is rejected on one it wasn't — | ||
| 138 | + /// Native Currency Substitution (docs/notes/native-currency-substitution.md). | ||
| 139 | + function test_RevertWhen_RegisteringCanonicalWethOnWrongChain() public { | ||
| 140 | + IWETH weth = etchWeth(OTHER_WETH_AT, WETH9_CANONICAL); | ||
| 141 | + deal(address(this), 1 ether); | ||
| 142 | + | ||
| 143 | + vm.chainId(137); // polygon pos, native token POL, never reviewed for this hash | ||
| 144 | + vm.expectRevert( | ||
| 145 | + abi.encodeWithSelector( | ||
| 146 | + WETHRegistry.UnknownImplementation.selector, IWETH(OTHER_WETH_AT), OTHER_WETH_AT.codehash, uint256(137) | ||
| 147 | + ) | ||
| 148 | + ); | ||
| 149 | + registry.register{value: probe}(weth); | ||
| 80 | } | 150 | } |
| 81 | 151 | ||
| 82 | /*////////////////////////////////////////////////////////////// | 152 | /*////////////////////////////////////////////////////////////// |
| @@ -91,6 +161,7 @@ contract WETHRegistryTest is Test, Fixtures { | |||
| 91 | } | 161 | } |
| 92 | 162 | ||
| 93 | function test_RegistersCanonicalWeth() public { | 163 | function test_RegistersCanonicalWeth() public { |
| 164 | + vm.chainId(WETH9_CANONICAL_CHAIN_ID); | ||
| 94 | IWETH weth = etchWeth(OTHER_WETH_AT, WETH9_CANONICAL); | 165 | IWETH weth = etchWeth(OTHER_WETH_AT, WETH9_CANONICAL); |
| 95 | register(registry, weth); | 166 | register(registry, weth); |
| 96 | 167 | ||
| @@ -144,7 +215,10 @@ contract WETHRegistryTest is Test, Fixtures { | |||
| 144 | 215 | ||
| 145 | vm.expectRevert( | 216 | vm.expectRevert( |
| 146 | abi.encodeWithSelector( | 217 | abi.encodeWithSelector( |
| 147 | - WETHRegistry.UnknownImplementation.selector, IWETH(address(mock)), address(mock).codehash | 218 | + WETHRegistry.UnknownImplementation.selector, |
| 219 | + IWETH(address(mock)), | ||
| 220 | + address(mock).codehash, | ||
| 221 | + WETH9_OP_LEGACY_CHAIN_ID | ||
| 148 | ) | 222 | ) |
| 149 | ); | 223 | ); |
| 150 | registry.register{value: probe}(IWETH(address(mock))); | 224 | registry.register{value: probe}(IWETH(address(mock))); |
| @@ -154,7 +228,11 @@ contract WETHRegistryTest is Test, Fixtures { | |||
| 154 | address empty = makeAddr("empty"); | 228 | address empty = makeAddr("empty"); |
| 155 | deal(address(this), 1 ether); | 229 | deal(address(this), 1 ether); |
| 156 | 230 | ||
| 157 | - vm.expectRevert(abi.encodeWithSelector(WETHRegistry.UnknownImplementation.selector, IWETH(empty), bytes32(0))); | 231 | + vm.expectRevert( |
| 232 | + abi.encodeWithSelector( | ||
| 233 | + WETHRegistry.UnknownImplementation.selector, IWETH(empty), bytes32(0), WETH9_OP_LEGACY_CHAIN_ID | ||
| 234 | + ) | ||
| 235 | + ); | ||
| 158 | registry.register{value: probe}(IWETH(empty)); | 236 | registry.register{value: probe}(IWETH(empty)); |
| 159 | } | 237 | } |
| 160 | 238 | ||