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::{
7 sync::{
8 Arc,
9 atomic::{AtomicBool, Ordering},
10 },
11 time::Instant,
12};
13
14const DEPLOYER: &str = "0x4e59b44847b379578588920ca78fbf26c0b4956c";
16
17#[derive(Clone, Debug, Parser)]
19pub struct Create2Args {
20 #[arg(
22 long,
23 short,
24 required_unless_present_any = &["ends_with", "matching", "salt"],
25 value_name = "HEX"
26 )]
27 starts_with: Option<String>,
28
29 #[arg(long, short, value_name = "HEX")]
31 ends_with: Option<String>,
32
33 #[arg(long, short, value_name = "HEX")]
35 matching: Option<String>,
36
37 #[arg(short, long)]
39 case_sensitive: bool,
40
41 #[arg(
43 short,
44 long,
45 default_value = DEPLOYER,
46 value_name = "ADDRESS"
47 )]
48 deployer: Address,
49
50 #[arg(
53 long,
54 conflicts_with_all = [
55 "starts_with",
56 "ends_with",
57 "matching",
58 "case_sensitive",
59 "caller",
60 "seed",
61 "no_random"
62 ],
63 value_name = "HEX"
64 )]
65 salt: Option<String>,
66
67 #[arg(short, long, value_name = "HEX")]
69 init_code: Option<String>,
70
71 #[arg(alias = "ch", long, value_name = "HASH", required_unless_present = "init_code")]
73 init_code_hash: Option<String>,
74
75 #[arg(global = true, long, short = 'j', visible_alias = "jobs")]
77 threads: Option<usize>,
78
79 #[arg(long, value_name = "ADDRESS")]
81 caller: Option<Address>,
82
83 #[arg(long, value_name = "HEX")]
85 seed: Option<B256>,
86
87 #[arg(long, conflicts_with = "seed")]
89 no_random: bool,
90}
91
92pub struct Create2Output {
93 pub address: Address,
94 pub salt: B256,
95}
96
97impl Create2Args {
98 pub fn run(self) -> Result<Create2Output> {
99 let Self {
100 starts_with,
101 ends_with,
102 matching,
103 case_sensitive,
104 deployer,
105 salt,
106 init_code,
107 init_code_hash,
108 threads,
109 caller,
110 seed,
111 no_random,
112 } = self;
113
114 let init_code_hash = if let Some(init_code_hash) = init_code_hash {
115 hex::FromHex::from_hex(init_code_hash)
116 } else if let Some(init_code) = init_code {
117 hex::decode(init_code).map(keccak256)
118 } else {
119 unreachable!();
120 }?;
121
122 if let Some(salt) = salt {
123 let salt = hex::FromHex::from_hex(salt)?;
124 let address = deployer.create2(salt, init_code_hash);
125 sh_println!("{address}")?;
126 return Ok(Create2Output { address, salt });
127 }
128
129 let mut regexs = vec![];
130
131 if let Some(matches) = matching {
132 if starts_with.is_some() || ends_with.is_some() {
133 eyre::bail!("Either use --matching or --starts/ends-with");
134 }
135
136 let matches = matches.trim_start_matches("0x");
137
138 if matches.len() != 40 {
139 eyre::bail!("Please provide a 40 characters long sequence for matching");
140 }
141
142 hex::decode(matches.replace('X', "0")).wrap_err("invalid matching hex provided")?;
143 regexs.push(matches.replace('X', "."));
146 }
147
148 if let Some(prefix) = starts_with {
149 regexs.push(format!(
150 r"^{}",
151 get_regex_hex_string(prefix).wrap_err("invalid prefix hex provided")?
152 ));
153 }
154 if let Some(suffix) = ends_with {
155 regexs.push(format!(
156 r"{}$",
157 get_regex_hex_string(suffix).wrap_err("invalid suffix hex provided")?
158 ))
159 }
160
161 debug_assert!(
162 regexs.iter().map(|p| p.len() - 1).sum::<usize>() <= 40,
163 "vanity patterns length exceeded. cannot be more than 40 characters",
164 );
165
166 let regex = RegexSetBuilder::new(regexs).case_insensitive(!case_sensitive).build()?;
167
168 let mut n_threads = threads.unwrap_or(0);
169 if n_threads == 0 {
170 n_threads = std::thread::available_parallelism().map_or(1, |n| n.get());
171 }
172 if cfg!(test) {
173 n_threads = n_threads.min(2);
174 }
175
176 let mut salt = B256::ZERO;
177 let remaining = if let Some(caller_address) = caller {
178 salt[..20].copy_from_slice(&caller_address.into_array());
179 &mut salt[20..]
180 } else {
181 &mut salt[..]
182 };
183
184 if !no_random {
185 let mut rng = match seed {
186 Some(seed) => StdRng::from_seed(seed.0),
187 None => StdRng::from_os_rng(),
188 };
189 rng.fill_bytes(remaining);
190 }
191
192 sh_println!("Configuration:")?;
193 sh_println!("Init code hash: {init_code_hash}")?;
194 sh_println!("Regex patterns: {:?}\n", regex.patterns())?;
195 sh_println!(
196 "Starting to generate deterministic contract address with {n_threads} threads..."
197 )?;
198 let mut handles = Vec::with_capacity(n_threads);
199 let found = Arc::new(AtomicBool::new(false));
200 let timer = Instant::now();
201
202 for i in 0..n_threads {
205 let increment = n_threads;
207 let regex = regex.clone();
208 let regex_len = regex.patterns().len();
209 let found = Arc::clone(&found);
210 handles.push(std::thread::spawn(move || {
211 struct B256Aligned(B256, [usize; 0]);
213 let mut salt = B256Aligned(salt, []);
214 let salt_word = unsafe {
216 &mut *salt.0.as_mut_ptr().add(32 - usize::BITS as usize / 8).cast::<usize>()
217 };
218 *salt_word = salt_word.wrapping_add(i);
220
221 let mut checksum_buf = [0u8; 42];
224 let mut hex_buf = [0u8; 40];
225 loop {
226 if found.load(Ordering::Relaxed) {
228 break None;
229 }
230
231 #[expect(clippy::needless_borrows_for_generic_args)]
233 let addr = deployer.create2(&salt.0, &init_code_hash);
234
235 let s = if case_sensitive {
239 let _ = addr.to_checksum_raw(&mut checksum_buf, None);
240 unsafe { std::str::from_utf8_unchecked(checksum_buf.get_unchecked(2..)) }
243 } else {
244 let _ = hex::encode_to_slice(addr.as_slice(), &mut hex_buf);
246 unsafe { std::str::from_utf8_unchecked(&hex_buf) }
247 };
248 if regex.matches(s).into_iter().count() == regex_len {
249 found.store(true, Ordering::Relaxed);
251 break Some((addr, salt.0));
252 }
253
254 *salt_word = salt_word.wrapping_add(increment);
256 }
257 }));
258 }
259
260 let results = handles.into_iter().filter_map(|h| h.join().unwrap()).collect::<Vec<_>>();
261 let (address, salt) = results.into_iter().next().unwrap();
262 sh_println!("Successfully found contract address in {:?}", timer.elapsed())?;
263 sh_println!("Address: {address}")?;
264 sh_println!("Salt: {salt} ({})", U256::from_be_bytes(salt.0))?;
265
266 Ok(Create2Output { address, salt })
267 }
268}
269
270fn get_regex_hex_string(s: String) -> Result<String> {
271 let s = s.strip_prefix("0x").unwrap_or(&s);
272 let pad_width = s.len() + s.len() % 2;
273 hex::decode(format!("{s:0<pad_width$}"))?;
274 Ok(s.to_string())
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280 use alloy_primitives::{address, b256};
281 use std::str::FromStr;
282
283 #[test]
284 fn basic_create2() {
285 let mk_args = |args: &[&str]| {
286 Create2Args::parse_from(["foundry-cli", "--init-code-hash=0x0000000000000000000000000000000000000000000000000000000000000000"].iter().chain(args))
287 };
288
289 let args = mk_args(&["--starts-with", "aa"]);
291 let create2_out = args.run().unwrap();
292 assert!(format!("{:x}", create2_out.address).starts_with("aa"));
293
294 let args = mk_args(&["--ends-with", "bb"]);
295 let create2_out = args.run().unwrap();
296 assert!(format!("{:x}", create2_out.address).ends_with("bb"));
297
298 let args = mk_args(&["--starts-with", "aaa"]);
300 let create2_out = args.run().unwrap();
301 assert!(format!("{:x}", create2_out.address).starts_with("aaa"));
302
303 let args = mk_args(&["--ends-with", "bbb"]);
304 let create2_out = args.run().unwrap();
305 assert!(format!("{:x}", create2_out.address).ends_with("bbb"));
306
307 let args = mk_args(&["--starts-with", "0xaa"]);
309 let create2_out = args.run().unwrap();
310 assert!(format!("{:x}", create2_out.address).starts_with("aa"));
311
312 let args = mk_args(&["--starts-with", "0xaaa"]);
314 let create2_out = args.run().unwrap();
315 assert!(format!("{:x}", create2_out.address).starts_with("aaa"));
316
317 let args = mk_args(&["--starts-with", "0xerr"]);
319 let create2_out = args.run();
320 assert!(create2_out.is_err());
321
322 let args = mk_args(&["--starts-with", "x00"]);
324 let create2_out = args.run();
325 assert!(create2_out.is_err());
326 }
327
328 #[test]
329 fn matches_pattern() {
330 let args = Create2Args::parse_from([
331 "foundry-cli",
332 "--init-code-hash=0x0000000000000000000000000000000000000000000000000000000000000000",
333 "--matching=0xbbXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
334 ]);
335 let create2_out = args.run().unwrap();
336 let address = create2_out.address;
337 assert!(format!("{address:x}").starts_with("bb"));
338 }
339
340 #[test]
341 fn create2_salt() {
342 let args = Create2Args::parse_from([
343 "foundry-cli",
344 "--deployer=0x8ba1f109551bD432803012645Ac136ddd64DBA72",
345 "--salt=0x7c5ea36004851c764c44143b1dcb59679b11c9a68e5f41497f6cf3d480715331",
346 "--init-code=0x6394198df16000526103ff60206004601c335afa6040516060f3",
347 ]);
348 let create2_out = args.run().unwrap();
349 let address = create2_out.address;
350 assert_eq!(address, address!("0x533AE9D683B10C02EBDB05471642F85230071FC3"));
351 }
352
353 #[test]
354 fn create2_init_code() {
355 let init_code = "00";
356 let args =
357 Create2Args::parse_from(["foundry-cli", "--starts-with=cc", "--init-code", init_code]);
358 let create2_out = args.run().unwrap();
359 let address = create2_out.address;
360 assert!(format!("{address:x}").starts_with("cc"));
361 let salt = create2_out.salt;
362 let deployer = Address::from_str(DEPLOYER).unwrap();
363 assert_eq!(address, deployer.create2_from_code(salt, hex::decode(init_code).unwrap()));
364 }
365
366 #[test]
367 fn create2_init_code_hash() {
368 let init_code_hash = "bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a";
369 let args = Create2Args::parse_from([
370 "foundry-cli",
371 "--starts-with=dd",
372 "--init-code-hash",
373 init_code_hash,
374 ]);
375 let create2_out = args.run().unwrap();
376 let address = create2_out.address;
377 assert!(format!("{address:x}").starts_with("dd"));
378
379 let salt = create2_out.salt;
380 let deployer = Address::from_str(DEPLOYER).unwrap();
381
382 assert_eq!(
383 address,
384 deployer
385 .create2(salt, B256::from_slice(hex::decode(init_code_hash).unwrap().as_slice()))
386 );
387 }
388
389 #[test]
390 fn create2_caller() {
391 let init_code_hash = "bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a";
392 let args = Create2Args::parse_from([
393 "foundry-cli",
394 "--starts-with=dd",
395 "--init-code-hash",
396 init_code_hash,
397 "--caller=0x66f9664f97F2b50F62D13eA064982f936dE76657",
398 ]);
399 let create2_out = args.run().unwrap();
400 let address = create2_out.address;
401 let salt = create2_out.salt;
402 assert!(format!("{address:x}").starts_with("dd"));
403 assert!(format!("{salt:x}").starts_with("66f9664f97f2b50f62d13ea064982f936de76657"));
404 }
405
406 #[test]
407 fn deterministic_seed() {
408 let args = Create2Args::parse_from([
409 "foundry-cli",
410 "--starts-with=0x00",
411 "--init-code-hash=0x479d7e8f31234e208d704ba1a123c76385cea8a6981fd675b784fbd9cffb918d",
412 "--seed=0x479d7e8f31234e208d704ba1a123c76385cea8a6981fd675b784fbd9cffb918d",
413 "-j1",
414 ]);
415 let out = args.run().unwrap();
416 assert_eq!(out.address, address!("0x00614b3D65ac4a09A376a264fE1aE5E5E12A6C43"));
417 assert_eq!(
418 out.salt,
419 b256!("0x322113f523203e2c0eb00bbc8e69208b0eb0c8dad0eaac7b01d64ff016edb40d"),
420 );
421 }
422
423 #[test]
424 fn deterministic_output() {
425 let args = Create2Args::parse_from([
426 "foundry-cli",
427 "--starts-with=0x00",
428 "--init-code-hash=0x479d7e8f31234e208d704ba1a123c76385cea8a6981fd675b784fbd9cffb918d",
429 "--no-random",
430 "-j1",
431 ]);
432 let out = args.run().unwrap();
433 assert_eq!(out.address, address!("0x00bF495b8b42fdFeb91c8bCEB42CA4eE7186AEd2"));
434 assert_eq!(
435 out.salt,
436 b256!("0x000000000000000000000000000000000000000000000000df00000000000000"),
437 );
438 }
439
440 #[test]
441 fn j0() {
442 let args = Create2Args::try_parse_from([
443 "foundry-cli",
444 "--starts-with=00",
445 "--init-code-hash",
446 &B256::ZERO.to_string(),
447 "-j0",
448 ])
449 .unwrap();
450 assert_eq!(args.threads, Some(0));
451 }
452}