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}")?;
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_println!("Configuration:")?;
187 sh_println!("Init code hash: {init_code_hash}")?;
188 sh_println!("Regex patterns: {:?}\n", regex.patterns())?;
189 sh_println!(
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_println!("Successfully found contract address in {:?}", timer.elapsed())?;
214 sh_println!("Address: {address}")?;
215 sh_println!("Salt: {salt} ({})", U256::from_be_bytes(salt.0))?;
216
217 Ok(Create2Output { address, salt })
218 }
219}
220
221fn get_regex_hex_string(s: String) -> Result<String> {
222 let s = s.strip_prefix("0x").unwrap_or(&s);
223 let pad_width = s.len() + s.len() % 2;
224 hex::decode(format!("{s:0<pad_width$}"))?;
225 Ok(s.to_string())
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231 use alloy_primitives::{address, b256};
232 use std::str::FromStr;
233
234 #[test]
235 fn basic_create2() {
236 let mk_args = |args: &[&str]| {
237 Create2Args::parse_from(["foundry-cli", "--init-code-hash=0x0000000000000000000000000000000000000000000000000000000000000000"].iter().chain(args))
238 };
239
240 let args = mk_args(&["--starts-with", "aa"]);
242 let create2_out = args.run().unwrap();
243 assert!(format!("{:x}", create2_out.address).starts_with("aa"));
244
245 let args = mk_args(&["--ends-with", "bb"]);
246 let create2_out = args.run().unwrap();
247 assert!(format!("{:x}", create2_out.address).ends_with("bb"));
248
249 let args = mk_args(&["--starts-with", "aaa"]);
251 let create2_out = args.run().unwrap();
252 assert!(format!("{:x}", create2_out.address).starts_with("aaa"));
253
254 let args = mk_args(&["--ends-with", "bbb"]);
255 let create2_out = args.run().unwrap();
256 assert!(format!("{:x}", create2_out.address).ends_with("bbb"));
257
258 let args = mk_args(&["--starts-with", "0xaa"]);
260 let create2_out = args.run().unwrap();
261 assert!(format!("{:x}", create2_out.address).starts_with("aa"));
262
263 let args = mk_args(&["--starts-with", "0xaaa"]);
265 let create2_out = args.run().unwrap();
266 assert!(format!("{:x}", create2_out.address).starts_with("aaa"));
267
268 let args = mk_args(&["--starts-with", "0xerr"]);
270 let create2_out = args.run();
271 assert!(create2_out.is_err());
272
273 let args = mk_args(&["--starts-with", "x00"]);
275 let create2_out = args.run();
276 assert!(create2_out.is_err());
277 }
278
279 #[test]
280 fn matches_pattern() {
281 let args = Create2Args::parse_from([
282 "foundry-cli",
283 "--init-code-hash=0x0000000000000000000000000000000000000000000000000000000000000000",
284 "--matching=0xbbXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
285 ]);
286 let create2_out = args.run().unwrap();
287 let address = create2_out.address;
288 assert!(format!("{address:x}").starts_with("bb"));
289 }
290
291 #[test]
292 fn create2_salt() {
293 let args = Create2Args::parse_from([
294 "foundry-cli",
295 "--deployer=0x8ba1f109551bD432803012645Ac136ddd64DBA72",
296 "--salt=0x7c5ea36004851c764c44143b1dcb59679b11c9a68e5f41497f6cf3d480715331",
297 "--init-code=0x6394198df16000526103ff60206004601c335afa6040516060f3",
298 ]);
299 let create2_out = args.run().unwrap();
300 let address = create2_out.address;
301 assert_eq!(address, address!("0x533AE9D683B10C02EBDB05471642F85230071FC3"));
302 }
303
304 #[test]
305 fn create2_init_code() {
306 let init_code = "00";
307 let args =
308 Create2Args::parse_from(["foundry-cli", "--starts-with=cc", "--init-code", init_code]);
309 let create2_out = args.run().unwrap();
310 let address = create2_out.address;
311 assert!(format!("{address:x}").starts_with("cc"));
312 let salt = create2_out.salt;
313 let deployer = Address::from_str(DEPLOYER).unwrap();
314 assert_eq!(address, deployer.create2_from_code(salt, hex::decode(init_code).unwrap()));
315 }
316
317 #[test]
318 fn create2_init_code_hash() {
319 let init_code_hash = "bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a";
320 let args = Create2Args::parse_from([
321 "foundry-cli",
322 "--starts-with=dd",
323 "--init-code-hash",
324 init_code_hash,
325 ]);
326 let create2_out = args.run().unwrap();
327 let address = create2_out.address;
328 assert!(format!("{address:x}").starts_with("dd"));
329
330 let salt = create2_out.salt;
331 let deployer = Address::from_str(DEPLOYER).unwrap();
332
333 assert_eq!(
334 address,
335 deployer
336 .create2(salt, B256::from_slice(hex::decode(init_code_hash).unwrap().as_slice()))
337 );
338 }
339
340 #[test]
341 fn create2_caller() {
342 let init_code_hash = "bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a";
343 let args = Create2Args::parse_from([
344 "foundry-cli",
345 "--starts-with=dd",
346 "--init-code-hash",
347 init_code_hash,
348 "--caller=0x66f9664f97F2b50F62D13eA064982f936dE76657",
349 ]);
350 let create2_out = args.run().unwrap();
351 let address = create2_out.address;
352 let salt = create2_out.salt;
353 assert!(format!("{address:x}").starts_with("dd"));
354 assert!(format!("{salt:x}").starts_with("66f9664f97f2b50f62d13ea064982f936de76657"));
355 }
356
357 #[test]
358 fn deterministic_seed() {
359 let args = Create2Args::parse_from([
360 "foundry-cli",
361 "--starts-with=0x00",
362 "--init-code-hash=0x479d7e8f31234e208d704ba1a123c76385cea8a6981fd675b784fbd9cffb918d",
363 "--seed=0x479d7e8f31234e208d704ba1a123c76385cea8a6981fd675b784fbd9cffb918d",
364 "-j1",
365 ]);
366 let out = args.run().unwrap();
367 assert_eq!(out.address, address!("0x00614b3D65ac4a09A376a264fE1aE5E5E12A6C43"));
368 assert_eq!(
369 out.salt,
370 b256!("0x322113f523203e2c0eb00bbc8e69208b0eb0c8dad0eaac7b01d64ff016edb40d"),
371 );
372 }
373
374 #[test]
375 fn deterministic_output() {
376 let args = Create2Args::parse_from([
377 "foundry-cli",
378 "--starts-with=0x00",
379 "--init-code-hash=0x479d7e8f31234e208d704ba1a123c76385cea8a6981fd675b784fbd9cffb918d",
380 "--no-random",
381 "-j1",
382 ]);
383 let out = args.run().unwrap();
384 assert_eq!(out.address, address!("0x00bF495b8b42fdFeb91c8bCEB42CA4eE7186AEd2"));
385 assert_eq!(
386 out.salt,
387 b256!("0x000000000000000000000000000000000000000000000000df00000000000000"),
388 );
389 }
390
391 #[test]
392 fn j0() {
393 let args = Create2Args::try_parse_from([
394 "foundry-cli",
395 "--starts-with=00",
396 "--init-code-hash",
397 &B256::ZERO.to_string(),
398 "-j0",
399 ])
400 .unwrap();
401 assert_eq!(args.threads, Some(0));
402 }
403}