1use alloy_primitives::{hex, keccak256, Address, B256, U256};
2use clap::Parser;
3use eyre::{Result, WrapErr};
4use rand::{rngs::StdRng, RngCore, SeedableRng};
5use regex::RegexSetBuilder;
6use std::{
7 sync::{
8 atomic::{AtomicBool, Ordering},
9 Arc,
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 prefix 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 = std::thread::available_parallelism().map_or(1, |n| n.get());
169 if let Some(threads) = threads {
170 n_threads = n_threads.min(threads);
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_entropy(),
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 = [0; 42];
222 loop {
223 if found.load(Ordering::Relaxed) {
225 break None;
226 }
227
228 #[expect(clippy::needless_borrows_for_generic_args)]
230 let addr = deployer.create2(&salt.0, &init_code_hash);
231
232 let _ = addr.to_checksum_raw(&mut checksum, None);
234 let s = unsafe { std::str::from_utf8_unchecked(checksum.get_unchecked(2..)) };
237 if regex.matches(s).into_iter().count() == regex_len {
238 found.store(true, Ordering::Relaxed);
240 break Some((addr, salt.0));
241 }
242
243 *salt_word = salt_word.wrapping_add(increment);
245 }
246 }));
247 }
248
249 let results = handles.into_iter().filter_map(|h| h.join().unwrap()).collect::<Vec<_>>();
250 let (address, salt) = results.into_iter().next().unwrap();
251 sh_println!("Successfully found contract address in {:?}", timer.elapsed())?;
252 sh_println!("Address: {address}")?;
253 sh_println!("Salt: {salt} ({})", U256::from_be_bytes(salt.0))?;
254
255 Ok(Create2Output { address, salt })
256 }
257}
258
259fn get_regex_hex_string(s: String) -> Result<String> {
260 let s = s.strip_prefix("0x").unwrap_or(&s);
261 let pad_width = s.len() + s.len() % 2;
262 hex::decode(format!("{s:0<pad_width$}"))?;
263 Ok(s.to_string())
264}
265
266#[cfg(test)]
267mod tests {
268 use super::*;
269 use alloy_primitives::{address, b256};
270 use std::str::FromStr;
271
272 #[test]
273 fn basic_create2() {
274 let mk_args = |args: &[&str]| {
275 Create2Args::parse_from(["foundry-cli", "--init-code-hash=0x0000000000000000000000000000000000000000000000000000000000000000"].iter().chain(args))
276 };
277
278 let args = mk_args(&["--starts-with", "aa"]);
280 let create2_out = args.run().unwrap();
281 assert!(format!("{:x}", create2_out.address).starts_with("aa"));
282
283 let args = mk_args(&["--ends-with", "bb"]);
284 let create2_out = args.run().unwrap();
285 assert!(format!("{:x}", create2_out.address).ends_with("bb"));
286
287 let args = mk_args(&["--starts-with", "aaa"]);
289 let create2_out = args.run().unwrap();
290 assert!(format!("{:x}", create2_out.address).starts_with("aaa"));
291
292 let args = mk_args(&["--ends-with", "bbb"]);
293 let create2_out = args.run().unwrap();
294 assert!(format!("{:x}", create2_out.address).ends_with("bbb"));
295
296 let args = mk_args(&["--starts-with", "0xaa"]);
298 let create2_out = args.run().unwrap();
299 assert!(format!("{:x}", create2_out.address).starts_with("aa"));
300
301 let args = mk_args(&["--starts-with", "0xaaa"]);
303 let create2_out = args.run().unwrap();
304 assert!(format!("{:x}", create2_out.address).starts_with("aaa"));
305
306 let args = mk_args(&["--starts-with", "0xerr"]);
308 let create2_out = args.run();
309 assert!(create2_out.is_err());
310
311 let args = mk_args(&["--starts-with", "x00"]);
313 let create2_out = args.run();
314 assert!(create2_out.is_err());
315 }
316
317 #[test]
318 fn matches_pattern() {
319 let args = Create2Args::parse_from([
320 "foundry-cli",
321 "--init-code-hash=0x0000000000000000000000000000000000000000000000000000000000000000",
322 "--matching=0xbbXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
323 ]);
324 let create2_out = args.run().unwrap();
325 let address = create2_out.address;
326 assert!(format!("{address:x}").starts_with("bb"));
327 }
328
329 #[test]
330 fn create2_salt() {
331 let args = Create2Args::parse_from([
332 "foundry-cli",
333 "--deployer=0x8ba1f109551bD432803012645Ac136ddd64DBA72",
334 "--salt=0x7c5ea36004851c764c44143b1dcb59679b11c9a68e5f41497f6cf3d480715331",
335 "--init-code=0x6394198df16000526103ff60206004601c335afa6040516060f3",
336 ]);
337 let create2_out = args.run().unwrap();
338 let address = create2_out.address;
339 assert_eq!(address, address!("0x533AE9D683B10C02EBDB05471642F85230071FC3"));
340 }
341
342 #[test]
343 fn create2_init_code() {
344 let init_code = "00";
345 let args =
346 Create2Args::parse_from(["foundry-cli", "--starts-with=cc", "--init-code", init_code]);
347 let create2_out = args.run().unwrap();
348 let address = create2_out.address;
349 assert!(format!("{address:x}").starts_with("cc"));
350 let salt = create2_out.salt;
351 let deployer = Address::from_str(DEPLOYER).unwrap();
352 assert_eq!(address, deployer.create2_from_code(salt, hex::decode(init_code).unwrap()));
353 }
354
355 #[test]
356 fn create2_init_code_hash() {
357 let init_code_hash = "bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a";
358 let args = Create2Args::parse_from([
359 "foundry-cli",
360 "--starts-with=dd",
361 "--init-code-hash",
362 init_code_hash,
363 ]);
364 let create2_out = args.run().unwrap();
365 let address = create2_out.address;
366 assert!(format!("{address:x}").starts_with("dd"));
367
368 let salt = create2_out.salt;
369 let deployer = Address::from_str(DEPLOYER).unwrap();
370
371 assert_eq!(
372 address,
373 deployer
374 .create2(salt, B256::from_slice(hex::decode(init_code_hash).unwrap().as_slice()))
375 );
376 }
377
378 #[test]
379 fn create2_caller() {
380 let init_code_hash = "bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a";
381 let args = Create2Args::parse_from([
382 "foundry-cli",
383 "--starts-with=dd",
384 "--init-code-hash",
385 init_code_hash,
386 "--caller=0x66f9664f97F2b50F62D13eA064982f936dE76657",
387 ]);
388 let create2_out = args.run().unwrap();
389 let address = create2_out.address;
390 let salt = create2_out.salt;
391 assert!(format!("{address:x}").starts_with("dd"));
392 assert!(format!("{salt:x}").starts_with("66f9664f97f2b50f62d13ea064982f936de76657"));
393 }
394
395 #[test]
396 fn deterministic_seed() {
397 let args = Create2Args::parse_from([
398 "foundry-cli",
399 "--starts-with=0x00",
400 "--init-code-hash=0x479d7e8f31234e208d704ba1a123c76385cea8a6981fd675b784fbd9cffb918d",
401 "--seed=0x479d7e8f31234e208d704ba1a123c76385cea8a6981fd675b784fbd9cffb918d",
402 "-j1",
403 ]);
404 let out = args.run().unwrap();
405 assert_eq!(out.address, address!("0x00614b3D65ac4a09A376a264fE1aE5E5E12A6C43"));
406 assert_eq!(
407 out.salt,
408 b256!("0x322113f523203e2c0eb00bbc8e69208b0eb0c8dad0eaac7b01d64ff016edb40d"),
409 );
410 }
411
412 #[test]
413 fn deterministic_output() {
414 let args = Create2Args::parse_from([
415 "foundry-cli",
416 "--starts-with=0x00",
417 "--init-code-hash=0x479d7e8f31234e208d704ba1a123c76385cea8a6981fd675b784fbd9cffb918d",
418 "--no-random",
419 "-j1",
420 ]);
421 let out = args.run().unwrap();
422 assert_eq!(out.address, address!("0x00bF495b8b42fdFeb91c8bCEB42CA4eE7186AEd2"));
423 assert_eq!(
424 out.salt,
425 b256!("0x000000000000000000000000000000000000000000000000df00000000000000"),
426 );
427 }
428
429 #[test]
430 fn j0() {
431 let args = Create2Args::try_parse_from([
432 "foundry-cli",
433 "--starts-with=00",
434 "--init-code-hash",
435 &B256::ZERO.to_string(),
436 "-j0",
437 ])
438 .unwrap();
439 assert_eq!(args.threads, Some(0));
440 }
441}