1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
|
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
import {LoveScript} from "../script/Love.s.sol";
import {Love} from "../src/Love.sol";
import {Fixtures} from "./Fixtures.sol";
import {Test} from "forge-std/Test.sol";
/// @title LoveCreate2Test
/// @notice The deployment address now derives from the salt and the creation
/// code alone. wETH used to be a constructor argument and so part of
/// that creation code, which gave the token a different address on
/// every chain whose wETH sat elsewhere; it is read from the registry
/// instead. These tests pin the consequence: same salt, same address,
/// on any chain, whatever its wETH is and wherever it lives.
contract LoveCreate2Test is Test, Fixtures {
bytes32 constant SALT = keccak256("LOVE");
address constant OP_STACK_WETH = 0x4200000000000000000000000000000000000006;
address constant MAINNET_WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
LoveScript script;
function setUp() public {
script = new LoveScript();
// The canonical deterministic deployer, as it exists on live networks.
assertGt(CREATE2_FACTORY.code.length, 0, "create2 deployer missing");
}
/*//////////////////////////////////////////////////////////////
THE REGISTRY
//////////////////////////////////////////////////////////////*/
/// @dev `Love` has the registry's address compiled in, so if the registry's
/// creation code moves — an edit to the contract, a solc bump, a
/// change of optimizer settings — this fails rather than shipping a
/// token pointing at an empty address.
function test_LoveRegistryConstantMatchesDeterministicAddress() public {
setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY);
assertEq(new Love().REGISTRY(), script.registryAddress());
}
function test_RegistryLandsAtTheAddressLoveExpects() public {
assertEq(address(deployRegistry()), script.registryAddress());
}
/// @dev The registry takes no constructor arguments, which is what makes
/// its address the same everywhere.
function test_RegistryAddressIsChainAgnostic() public {
address onThisChain = script.registryAddress();
vm.chainId(137);
assertEq(script.registryAddress(), onThisChain);
vm.chainId(42_161);
assertEq(script.registryAddress(), onThisChain);
}
/*//////////////////////////////////////////////////////////////
THE DERIVATION
//////////////////////////////////////////////////////////////*/
function test_DeploysAtPredictedAddress() public {
setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY);
address predicted = vm.computeCreate2Address(SALT, keccak256(script.initCode()), CREATE2_FACTORY);
Love deployed = Love(_deploy(SALT));
assertEq(address(deployed), predicted);
assertEq(deployed.symbol(), "LOVE");
assertEq(address(deployed.WETH()), OP_STACK_WETH);
}
/// @dev What the deploy script prints must be what the chain gives back.
function test_ScriptPredictionMatchesDeployment() public {
setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY);
assertEq(script.predict(SALT), _deploy(SALT));
}
function test_ScriptUsesKeccakOfLoveAsDefaultSalt() public view {
assertEq(script.DEFAULT_SALT(), SALT);
}
function test_ScriptDefaultsToOpStackWeth() public view {
assertEq(script.DEFAULT_WETH(), OP_STACK_WETH);
}
/// @dev Same salt, same code, same address — and unlike before, no wETH
/// argument that could make the answer differ.
function test_PredictionIsChainAgnostic() public {
address onThisChain = script.predict(SALT);
vm.chainId(137);
assertEq(script.predict(SALT), onThisChain);
vm.chainId(42_161);
assertEq(script.predict(SALT), onThisChain);
}
function test_DifferentSaltsGiveDifferentAddresses() public view {
assertTrue(script.predict(SALT) != script.predict(keccak256("LOVE2")));
}
/*//////////////////////////////////////////////////////////////
THE POINT OF ALL THIS
//////////////////////////////////////////////////////////////*/
/// @dev The test that replaces `test_DifferentWethGivesDifferentAddress`.
/// Two chains, two different wETH implementations at two different
/// addresses, one LOVE address. This is what the registry buys.
function test_SameAddressOnChainsWithDifferentWeth() public {
// A snapshot stands in for a second chain. Etching the code away is
// not enough: a deployed account keeps its nonce, and CREATE2 refuses
// to build over that, so the registry could never be redeployed.
uint256 freshChain = vm.snapshotState();
setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY);
address onOpStack = _deploy(SALT);
assertEq(address(Love(onOpStack).WETH()), OP_STACK_WETH);
vm.revertToState(freshChain);
// Different chain id, a different wETH implementation, at a different
// address.
vm.chainId(1);
setUpChain(MAINNET_WETH, WETH9_CANONICAL);
address onMainnet = _deploy(SALT);
assertEq(address(Love(onMainnet).WETH()), MAINNET_WETH);
assertEq(onOpStack, onMainnet, "LOVE must land at one address on both chains");
}
/// @dev Redeploying with the same salt must fail, not silently return the
/// existing token.
function test_RevertWhen_RedeployingWithSameSalt() public {
setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY);
_deploy(SALT);
(bool ok,) = CREATE2_FACTORY.call(abi.encodePacked(SALT, script.initCode()));
assertFalse(ok);
}
function testFuzz_PredictionMatchesDeployment(bytes32 salt) public {
setUpChain(OP_STACK_WETH, WETH9_OP_LEGACY);
assertEq(script.predict(salt), _deploy(salt));
}
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR GUARDS
//////////////////////////////////////////////////////////////*/
function test_RevertWhen_RegistryNotDeployed() public {
vm.expectRevert(abi.encodeWithSelector(Love.RegistryNotDeployed.selector, script.registryAddress()));
new Love();
}
function test_RevertWhen_WethNotRegistered() public {
deployRegistry();
vm.expectRevert(abi.encodeWithSelector(Love.WethNotRegistered.selector, script.registryAddress()));
new Love();
}
/// @dev Deploys through the canonical deterministic deployer, as the script does.
function _deploy(bytes32 salt) internal returns (address deployed) {
(bool ok, bytes memory ret) = CREATE2_FACTORY.call(abi.encodePacked(salt, script.initCode()));
require(ok, "create2 deployment failed");
// casting to 'bytes20' is safe because the deployer returns the 20-byte address, raw
// forge-lint: disable-next-line(unsafe-typecast)
deployed = address(bytes20(ret));
}
}
|