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
176
177
178
179
180
181
182
183
184
185
186
187
188
|
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
import {IWETH} from "../src/IWETH.sol";
import {Love} from "../src/Love.sol";
import {WETHRegistry} from "../src/WETHRegistry.sol";
import {Script, console} from "forge-std/Script.sol";
/// @title LoveScript
/// @author Julien Béranger
/// @notice Brings LOVE up on a chain, in the order the design requires:
/// registry first, wETH registered second, token third.
/// @dev Both contracts go through the canonical deterministic deployer and
/// neither takes constructor arguments, so their creation code is
/// identical on every chain and so are their addresses. That is the whole
/// point — LOVE is one address everywhere, not one per wETH deployment.
/// Keep `solc`, the optimizer settings and `bytecode_hash = "none"` as
/// they are in `foundry.toml`, or both addresses move.
contract LoveScript is Script {
/// @notice Salt used for `Love` when `SALT` is not set in the environment.
bytes32 public constant DEFAULT_SALT = keccak256("LOVE");
/// @notice Salt the registry is deployed with. Not configurable: `Love`
/// has the resulting address compiled into it.
bytes32 public constant REGISTRY_SALT = keccak256("LOVE.WETHRegistry");
/// @notice wETH address used when `WETH` is not set in the environment.
/// @dev The OP Stack predeploy, which is where wETH sits on Optimism,
/// Base, Mode, Zora, Lisk, World Chain, Unichain, Soneium and Ink.
/// Chains outside that set need `WETH` set explicitly.
address public constant DEFAULT_WETH = 0x4200000000000000000000000000000000000006;
/// @notice The registry, once `run()` has deployed or found it.
WETHRegistry public registry;
/// @notice The token, once `run()` has deployed or found it.
Love public love;
/// @notice Deploy the registry, register wETH and deploy the token,
/// skipping whichever of those has already happened.
/// @dev Reads `SALT` and `WETH` from the environment, falling back to the
/// defaults, and asserts both deployments match their predictions.
/// @return The deployed token.
function run() public returns (Love) {
address w = wethAddress();
address predictedRegistry = registryAddress();
address predicted = predict(salt());
console.log("deployer ", CREATE2_FACTORY);
console.log("registry ", predictedRegistry);
console.log("weth ", w);
console.log("salt ", vm.toString(salt()));
console.log("initCodeHash ", vm.toString(initCodeHash()));
console.log("predicted ", predicted);
_ensureRegistry(predictedRegistry);
_ensureRegistered(IWETH(w));
_ensureLove(predicted);
return love;
}
/// @notice Print what `run()` would deploy, without broadcasting.
/// @return predicted The address the current salt derives to.
function predict() public view returns (address predicted) {
predicted = predict(salt());
console.log("registry ", registryAddress());
console.log("weth ", wethAddress());
console.log("salt ", vm.toString(salt()));
console.log("initCodeHash ", vm.toString(initCodeHash()));
console.log("predicted ", predicted);
}
/// @notice Derive the token's deployment address from a salt.
/// @dev Takes no wETH argument, unlike the address derivation this
/// replaces: wETH is no longer in the creation code, which is why the
/// answer is the same on every chain.
/// @param s The CREATE2 salt.
/// @return The address `Love` would land at.
function predict(bytes32 s) public pure returns (address) {
return vm.computeCreate2Address(s, initCodeHash(), CREATE2_FACTORY);
}
/// @notice The address the registry lands at on every chain.
/// @return The registry's CREATE2 address.
function registryAddress() public pure returns (address) {
return vm.computeCreate2Address(REGISTRY_SALT, keccak256(registryInitCode()), CREATE2_FACTORY);
}
/// @notice The token's creation code, which now carries no arguments.
/// @return The creation code handed to CREATE2.
function initCode() public pure returns (bytes memory) {
return type(Love).creationCode;
}
/// @notice Hash of the token's creation code.
/// @return The keccak256 of `initCode()`.
function initCodeHash() public pure returns (bytes32) {
return keccak256(initCode());
}
/// @notice The registry's creation code.
/// @return The creation code handed to CREATE2.
function registryInitCode() public pure returns (bytes memory) {
return type(WETHRegistry).creationCode;
}
/// @notice The salt to deploy the token with.
/// @dev `SALT` overrides the default, e.g. to mine a vanity address or to
/// route around a chain where the registry has been squatted.
/// @return The configured salt, or `DEFAULT_SALT`.
function salt() public view returns (bytes32) {
return vm.envOr("SALT", DEFAULT_SALT);
}
/// @notice The wETH address to register on this chain.
/// @dev `WETH` overrides the default, which only holds on OP Stack chains.
/// Ignored once a wETH is already registered.
/// @return The configured wETH address, or `DEFAULT_WETH`.
function wethAddress() public view returns (address) {
return vm.envOr("WETH", DEFAULT_WETH);
}
/// @dev Deploys the registry unless it is already there.
/// @param predicted Where it should land.
function _ensureRegistry(address predicted) private {
if (predicted.code.length > 0) {
console.log("registry already deployed");
registry = WETHRegistry(payable(predicted));
return;
}
vm.startBroadcast();
registry = new WETHRegistry{salt: REGISTRY_SALT}();
vm.stopBroadcast();
require(address(registry) == predicted, "LoveScript: registry address mismatch");
console.log("registry deployed at", address(registry));
}
/// @dev Registers `candidate` unless a wETH is already registered. Reverts
/// inside the registry if the candidate's code is not a reviewed wETH
/// — which is the intended outcome on a chain whose wETH has not been
/// reviewed, not a script failure to work around.
/// @param candidate The wETH to register.
function _ensureRegistered(IWETH candidate) private {
IWETH registered = registry.weth();
if (address(registered) != address(0)) {
console.log("weth already registered", address(registered));
require(
address(registered) == address(candidate) || address(candidate) == DEFAULT_WETH,
"LoveScript: a different weth is registered on this chain"
);
return;
}
vm.startBroadcast();
registry.register{value: registry.PROBE()}(candidate);
vm.stopBroadcast();
console.log("weth registered", address(registry.weth()));
}
/// @dev Deploys the token unless it is already there.
/// @param predicted Where it should land.
function _ensureLove(address predicted) private {
if (predicted.code.length > 0) {
console.log("already deployed, nothing to do");
love = Love(predicted);
return;
}
vm.startBroadcast();
love = new Love{salt: salt()}();
vm.stopBroadcast();
require(address(love) == predicted, "LoveScript: address mismatch");
console.log("Love deployed at", address(love));
console.log("name ", love.name());
console.log("symbol ", love.symbol());
console.log("weth ", address(love.WETH()));
console.log("rate ", love.RATE());
console.log("totalSupply ", love.totalSupply());
}
}
|