1use alloy_primitives::{Address, B256, U256, hex, keccak256};
2use clap::Parser;
3use eyre::{Result, WrapErr};
4use rand::{RngCore, SeedableRng, rngs::StdRng};
5use regex::RegexSetBuilder;
6use std::time::Instant;
7
8const DEPLOYER: &str = "0x4e59b44847b379578588920ca78fbf26c0b4956c";
10
11#[derive(Clone, Debug, Parser)]
13pub struct Create2Args {
14 #[arg(
16 long,
17 short,
18 required_unless_present_any = &["ends_with", "matching", "salt"],
19 value_name = "HEX"
20 )]
21 starts_with: Option<String>,
22
23 #[arg(long, short, value_name = "HEX")]
25 ends_with: Option<String>,
26
27 #[arg(long, short, value_name = "HEX")]
29 matching: Option<String>,
30
31 #[arg(short, long)]
33 case_sensitive: bool,
34
35 #[arg(
37 short,
38 long,
39 default_value = DEPLOYER,
40 value_name = "ADDRESS"
41 )]
42 deployer: Address,
43
44 #[arg(
47 long,
48 conflicts_with_all = [
49 "starts_with",
50 "ends_with",
51 "matching",
52 "case_sensitive",
53 "caller",
54 "seed",
55 "no_random"
56 ],
57 value_name = "HEX"
58 )]
59 salt: Option<String>,
60
61 #[arg(short, long, value_name = "HEX")]
63 init_code: Option<String>,
64
65 #[arg(alias = "ch", long, value_name = "HASH", required_unless_present = "init_code")]
67 init_code_hash: Option<String>,
68
69 #[arg(global = true, long, short = 'j', visible_alias = "jobs")]
71 threads: Option<usize>,
72
73 #[arg(long, value_name = "ADDRESS")]
75 caller: Option<Address>,
76
77 #[arg(long, value_name = "HEX")]
79 seed: Option<B256>,
80
81 #[arg(long, conflicts_with = "seed")]
83 no_random: bool,
84}
85
86pub struct Create2Output {
87 pub address: Address,
88 pub salt: B256,
89}
90
91impl Create2Args {
92 pub fn run(self) -> Result<Create2Output> {
93 let Self {
94 starts_with,
95 ends_with,
96 matching,
97 case_sensitive,
98 deployer,
99 salt,
100 init_code,
101 init_code_hash,
102 threads,
103 caller,
104 seed,
105 no_random,
106 } = self;
107
108 let init_code_hash = if let Some(init_code_hash) = init_code_hash {
109 hex::FromHex::from_hex(init_code_hash)
110 } else if let Some(init_code) = init_code {
111 hex::decode(init_code).map(keccak256)
112 } else {
113 unreachable!();
114 }?;
115
116 if let Some(salt) = salt {
117 let salt = hex::FromHex::from_hex(salt)?;
118 let address = deployer.create2(salt, init_code_hash);
119 sh_println!("{address}\t{salt}")?;
120 return Ok(Create2Output { address, salt });
121 }
122
123 let mut regexs = vec![];
124
125 if let Some(matches) = matching {
126 if starts_with.is_some() || ends_with.is_some() {
127 eyre::bail!("Either use --matching or --starts/ends-with");
128 }
129
130 let matches = matches.trim_start_matches("0x");
131
132 if matches.len() != 40 {
133 eyre::bail!("Please provide a 40 characters long sequence for matching");
134 }
135
136 hex::decode(matches.replace('X', "0")).wrap_err("invalid matching hex provided")?;
137 regexs.push(matches.replace('X', "."));
140 }
141
142 if let Some(prefix) = starts_with {
143 regexs.push(format!(
144 r"^{}",
145 get_regex_hex_string(prefix).wrap_err("invalid prefix hex provided")?
146 ));
147 }
148 if let Some(suffix) = ends_with {
149 regexs.push(format!(
150 r"{}$",
151 get_regex_hex_string(suffix).wrap_err("invalid suffix hex provided")?
152 ))
153 }
154
155 debug_assert!(
156 regexs.iter().map(|p| p.len() - 1).sum::<usize>() <= 40,
157 "vanity patterns length exceeded. cannot be more than 40 characters",
158 );
159
160 let regex = RegexSetBuilder::new(regexs).case_insensitive(!case_sensitive).build()?;
161
162 let mut n_threads = threads.unwrap_or(0);
163 if n_threads == 0 {
164 n_threads = std::thread::available_parallelism().map_or(1, |n| n.get());
165 }
166 if cfg!(test) {
167 n_threads = n_threads.min(2);
168 }
169
170 let mut salt = B256::ZERO;
171 let remaining = if let Some(caller_address) = caller {
172 salt[..20].copy_from_slice(&caller_address.into_array());
173 &mut salt[20..]
174 } else {
175 &mut salt[..]
176 };
177
178 if !no_random {
179 let mut rng = match seed {
180 Some(seed) => StdRng::from_seed(seed.0),
181 None => StdRng::from_os_rng(),
182 };
183 rng.fill_bytes(remaining);
184 }
185
186 sh_status!("Configuration:")?;
187 sh_status!("Init code hash: {init_code_hash}")?;
188 sh_status!("Regex patterns: {:?}\n", regex.patterns())?;
189 sh_status!(
190 "Starting to generate deterministic contract address with {n_threads} threads..."
191 )?;
192 let timer = Instant::now();
193 let regex_len = regex.patterns().len();
194 let mut checksum_buf = [0u8; 42];
195 let mut hex_buf = [0u8; 40];
196 let (address, salt) = super::miner::mine_salt(salt, n_threads, move |salt| {
197 #[expect(clippy::needless_borrows_for_generic_args)]
198 let addr = deployer.create2(&salt, &init_code_hash);
199 let s = if case_sensitive {
202 let _ = addr.to_checksum_raw(&mut checksum_buf, None);
203 unsafe { std::str::from_utf8_unchecked(checksum_buf.get_unchecked(2..)) }
205 } else {
206 let _ = hex::encode_to_slice(addr.as_slice(), &mut hex_buf);
208 unsafe { std::str::from_utf8_unchecked(&hex_buf) }
209 };
210 (regex.matches(s).into_iter().count() == regex_len).then_some((addr, salt))
211 })
212 .ok_or_else(|| eyre::eyre!("create2 salt mining failed: all threads panicked"))?;
213 sh_status!("Successfully found contract address in {:?}", timer.elapsed())?;
214 sh_status!("Address: {address}")?;
215 sh_status!("Salt: {salt} ({})", U256::from_be_bytes(salt.0))?;
216 sh_println!("{address}\t{salt}")?;
217
218 Ok(Create2Output { address, salt })
219 }
220}
221
222fn get_regex_hex_string(s: String) -> Result<String> {
223 let s = s.strip_prefix("0x").unwrap_or(&s);
224 let pad_width = s.len() + s.len() % 2;
225 hex::decode(format!("{s:0<pad_width$}"))?;
226 Ok(s.to_string())
227}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232 use alloy_primitives::{address, b256};
233 use std::str::FromStr;
234
235 #[test]
236 fn basic_create2() {
237 let mk_args = |args: &[&str]| {
238 Create2Args::parse_from(["foundry-cli", "--init-code-hash=0x0000000000000000000000000000000000000000000000000000000000000000"].iter().chain(args))
239 };
240
241 let args = mk_args(&["--starts-with", "aa"]);
243 let create2_out = args.run().unwrap();
244 assert!(format!("{:x}", create2_out.address).starts_with("aa"));
245
246 let args = mk_args(&["--ends-with", "bb"]);
247 let create2_out = args.run().unwrap();
248 assert!(format!("{:x}", create2_out.address).ends_with("bb"));
249
250 let args = mk_args(&["--starts-with", "aaa"]);
252 let create2_out = args.run().unwrap();
253 assert!(format!("{:x}", create2_out.address).starts_with("aaa"));
254
255 let args = mk_args(&["--ends-with", "bbb"]);
256 let create2_out = args.run().unwrap();
257 assert!(format!("{:x}", create2_out.address).ends_with("bbb"));
258
259 let args = mk_args(&["--starts-with", "0xaa"]);
261 let create2_out = args.run().unwrap();
262 assert!(format!("{:x}", create2_out.address).starts_with("aa"));
263
264 let args = mk_args(&["--starts-with", "0xaaa"]);
266 let create2_out = args.run().unwrap();
267 assert!(format!("{:x}", create2_out.address).starts_with("aaa"));
268
269 let args = mk_args(&["--starts-with", "0xerr"]);
271 let create2_out = args.run();
272 assert!(create2_out.is_err());
273
274 let args = mk_args(&["--starts-with", "x00"]);
276 let create2_out = args.run();
277 assert!(create2_out.is_err());
278 }
279
280 #[test]
281 fn matches_pattern() {
282 let args = Create2Args::parse_from([
283 "foundry-cli",
284 "--init-code-hash=0x0000000000000000000000000000000000000000000000000000000000000000",
285 "--matching=0xbbXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
286 ]);
287 let create2_out = args.run().unwrap();
288 let address = create2_out.address;
289 assert!(format!("{address:x}").starts_with("bb"));
290 }
291
292 #[test]
293 fn create2_salt() {
294 let args = Create2Args::parse_from([
295 "foundry-cli",
296 "--deployer=0x8ba1f109551bD432803012645Ac136ddd64DBA72",
297 "--salt=0x7c5ea36004851c764c44143b1dcb59679b11c9a68e5f41497f6cf3d480715331",
298 "--init-code=0x6394198df16000526103ff60206004601c335afa6040516060f3",
299 ]);
300 let create2_out = args.run().unwrap();
301 let address = create2_out.address;
302 assert_eq!(address, address!("0x533AE9D683B10C02EBDB05471642F85230071FC3"));
303 }
304
305 #[test]
306 fn create2_init_code() {
307 let init_code = "00";
308 let args =
309 Create2Args::parse_from(["foundry-cli", "--starts-with=cc", "--init-code", init_code]);
310 let create2_out = args.run().unwrap();
311 let address = create2_out.address;
312 assert!(format!("{address:x}").starts_with("cc"));
313 let salt = create2_out.salt;
314 let deployer = Address::from_str(DEPLOYER).unwrap();
315 assert_eq!(address, deployer.create2_from_code(salt, hex::decode(init_code).unwrap()));
316 }
317
318 #[test]
319 fn create2_init_code_hash() {
320 let init_code_hash = "bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a";
321 let args = Create2Args::parse_from([
322 "foundry-cli",
323 "--starts-with=dd",
324 "--init-code-hash",
325 init_code_hash,
326 ]);
327 let create2_out = args.run().unwrap();
328 let address = create2_out.address;
329 assert!(format!("{address:x}").starts_with("dd"));
330
331 let salt = create2_out.salt;
332 let deployer = Address::from_str(DEPLOYER).unwrap();
333
334 assert_eq!(
335 address,
336 deployer
337 .create2(salt, B256::from_slice(hex::decode(init_code_hash).unwrap().as_slice()))
338 );
339 }
340
341 #[test]
342 fn create2_caller() {
343 let init_code_hash = "bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a";
344 let args = Create2Args::parse_from([
345 "foundry-cli",
346 "--starts-with=dd",
347 "--init-code-hash",
348 init_code_hash,
349 "--caller=0x66f9664f97F2b50F62D13eA064982f936dE76657",
350 ]);
351 let create2_out = args.run().unwrap();
352 let address = create2_out.address;
353 let salt = create2_out.salt;
354 assert!(format!("{address:x}").starts_with("dd"));
355 assert!(format!("{salt:x}").starts_with("66f9664f97f2b50f62d13ea064982f936de76657"));
356 }
357
358 #[test]
359 fn deterministic_seed() {
360 let args = Create2Args::parse_from([
361 "foundry-cli",
362 "--starts-with=0x00",
363 "--init-code-hash=0x479d7e8f31234e208d704ba1a123c76385cea8a6981fd675b784fbd9cffb918d",
364 "--seed=0x479d7e8f31234e208d704ba1a123c76385cea8a6981fd675b784fbd9cffb918d",
365 "-j1",
366 ]);
367 let out = args.run().unwrap();
368 assert_eq!(out.address, address!("0x00614b3D65ac4a09A376a264fE1aE5E5E12A6C43"));
369 assert_eq!(
370 out.salt,
371 b256!("0x322113f523203e2c0eb00bbc8e69208b0eb0c8dad0eaac7b01d64ff016edb40d"),
372 );
373 }
374
375 #[test]
376 fn deterministic_output() {
377 let args = Create2Args::parse_from([
378 "foundry-cli",
379 "--starts-with=0x00",
380 "--init-code-hash=0x479d7e8f31234e208d704ba1a123c76385cea8a6981fd675b784fbd9cffb918d",
381 "--no-random",
382 "-j1",
383 ]);
384 let out = args.run().unwrap();
385 assert_eq!(out.address, address!("0x00bF495b8b42fdFeb91c8bCEB42CA4eE7186AEd2"));
386 assert_eq!(
387 out.salt,
388 b256!("0x000000000000000000000000000000000000000000000000df00000000000000"),
389 );
390 }
391
392 #[test]
393 fn j0() {
394 let args = Create2Args::try_parse_from([
395 "foundry-cli",
396 "--starts-with=00",
397 "--init-code-hash",
398 &B256::ZERO.to_string(),
399 "-j0",
400 ])
401 .unwrap();
402 assert_eq!(args.threads, Some(0));
403 }
404}