1use alloy_dyn_abi::JsonAbiExt;
2use alloy_primitives::{Address, B256, U256, hex, keccak256};
3use clap::{Args, Parser, Subcommand};
4use eyre::{Result, WrapErr};
5use foundry_cli::{
6 json::print_scalar,
7 opts::BuildOpts,
8 utils::{LoadConfig, find_contract_artifacts, parse_constructor_args},
9};
10use foundry_common::{compile, shell};
11use foundry_compilers::{info::ContractInfo, utils::canonicalize};
12use rand::{RngCore, SeedableRng, rngs::StdRng};
13use regex::RegexSetBuilder;
14use std::time::Instant;
15
16const DEPLOYER: &str = "0x4e59b44847b379578588920ca78fbf26c0b4956c";
18
19#[derive(Clone, Debug, Subcommand)]
20enum Create2Subcommand {
21 #[command(visible_alias = "initcodehash")]
23 InitCodeHash(InitCodeHashArgs),
24}
25
26foundry_config::impl_figment_convert!(InitCodeHashArgs, build);
27
28#[derive(Clone, Debug, Args)]
29struct InitCodeHashArgs {
30 contract: ContractInfo,
32
33 #[arg(value_name = "ARGS", allow_negative_numbers = true)]
35 constructor_args: Vec<String>,
36
37 #[command(flatten)]
38 build: BuildOpts,
39}
40
41impl Create2Subcommand {
42 fn run(&self) -> Result<()> {
43 match self {
44 Self::InitCodeHash(args) => args.run(),
45 }
46 }
47}
48
49impl InitCodeHashArgs {
50 fn run(&self) -> Result<()> {
51 let config = self.load_config()?;
52 let project = config.project()?;
53 let target_path = if let Some(path) = &self.contract.path {
54 canonicalize(project.root().join(path))?
55 } else {
56 project.find_contract_path(&self.contract.name)?
57 };
58
59 let output = compile::compile_target(&target_path, &project, true)?;
60 let (abi, bin, _) = find_contract_artifacts(output, &target_path, &self.contract.name)?;
61 let Some(bytecode) = bin.object.into_bytes() else {
62 eyre::bail!("contract contains unlinked libraries");
63 };
64 if bytecode.is_empty() {
65 eyre::bail!("no bytecode found in bin object for {}", self.contract.name);
66 }
67
68 let mut init_code = bytecode.to_vec();
69 if let Some(constructor) = &abi.constructor {
70 let params = parse_constructor_args(constructor, &self.constructor_args)?;
71 init_code.extend(constructor.abi_encode_input(¶ms)?);
72 } else if !self.constructor_args.is_empty() {
73 eyre::bail!("contract does not have a constructor");
74 }
75
76 print_scalar(keccak256(init_code))?;
77 Ok(())
78 }
79}
80
81#[derive(Clone, Debug, Parser)]
83#[command(subcommand_negates_reqs = true, args_conflicts_with_subcommands = true)]
84pub struct Create2Args {
85 #[command(subcommand)]
86 command: Option<Create2Subcommand>,
87
88 #[arg(
90 long,
91 short,
92 required_unless_present_any = &["ends_with", "matching", "salt"],
93 value_name = "HEX"
94 )]
95 starts_with: Option<String>,
96
97 #[arg(long, short, value_name = "HEX")]
99 ends_with: Option<String>,
100
101 #[arg(long, short, value_name = "HEX")]
103 matching: Option<String>,
104
105 #[arg(short, long)]
107 case_sensitive: bool,
108
109 #[arg(
111 short,
112 long,
113 default_value = DEPLOYER,
114 value_name = "ADDRESS"
115 )]
116 deployer: Address,
117
118 #[arg(
121 long,
122 conflicts_with_all = [
123 "starts_with",
124 "ends_with",
125 "matching",
126 "case_sensitive",
127 "caller",
128 "seed",
129 "no_random"
130 ],
131 value_name = "HEX"
132 )]
133 salt: Option<String>,
134
135 #[arg(short, long, value_name = "HEX")]
137 init_code: Option<String>,
138
139 #[arg(alias = "ch", long, value_name = "HASH", required_unless_present = "init_code")]
141 init_code_hash: Option<String>,
142
143 #[arg(global = true, long, short = 'j', visible_alias = "jobs")]
145 threads: Option<usize>,
146
147 #[arg(long, value_name = "ADDRESS")]
149 caller: Option<Address>,
150
151 #[arg(long, value_name = "HEX")]
153 seed: Option<B256>,
154
155 #[arg(long, conflicts_with = "seed")]
157 no_random: bool,
158}
159
160pub struct Create2Output {
161 pub address: Address,
162 pub salt: B256,
163}
164
165impl Create2Args {
166 pub fn execute(self) -> Result<()> {
167 if let Some(command) = &self.command {
168 return command.run();
169 }
170 self.run().map(drop)
171 }
172
173 pub fn run(self) -> Result<Create2Output> {
174 if self.command.is_some() {
175 eyre::bail!(
176 "`Create2Args::run` does not support subcommands; use `Create2Args::execute` instead"
177 );
178 }
179
180 let Self {
181 command: _,
182 starts_with,
183 ends_with,
184 matching,
185 case_sensitive,
186 deployer,
187 salt,
188 init_code,
189 init_code_hash,
190 threads,
191 caller,
192 seed,
193 no_random,
194 } = self;
195
196 let init_code_hash = if let Some(init_code_hash) = init_code_hash {
197 hex::FromHex::from_hex(init_code_hash)
198 } else if let Some(init_code) = init_code {
199 hex::decode(init_code).map(keccak256)
200 } else {
201 unreachable!();
202 }?;
203
204 if let Some(salt) = salt {
205 let salt = hex::FromHex::from_hex(salt)?;
206 let address = deployer.create2(salt, init_code_hash);
207 sh_println!("{address}\t{salt}")?;
208 return Ok(Create2Output { address, salt });
209 }
210
211 let mut regexs = vec![];
212
213 if let Some(matches) = matching {
214 if starts_with.is_some() || ends_with.is_some() {
215 eyre::bail!("Either use --matching or --starts/ends-with");
216 }
217
218 let matches = matches.trim_start_matches("0x");
219
220 if matches.len() != 40 {
221 eyre::bail!("Please provide a 40 characters long sequence for matching");
222 }
223
224 hex::decode(matches.replace('X', "0")).wrap_err("invalid matching hex provided")?;
225 regexs.push(matches.replace('X', "."));
228 }
229
230 if let Some(prefix) = starts_with {
231 regexs.push(format!(
232 r"^{}",
233 get_regex_hex_string(prefix).wrap_err("invalid prefix hex provided")?
234 ));
235 }
236 if let Some(suffix) = ends_with {
237 regexs.push(format!(
238 r"{}$",
239 get_regex_hex_string(suffix).wrap_err("invalid suffix hex provided")?
240 ))
241 }
242
243 debug_assert!(
244 regexs.iter().map(|p| p.len() - 1).sum::<usize>() <= 40,
245 "vanity patterns length exceeded. cannot be more than 40 characters",
246 );
247
248 let regex = RegexSetBuilder::new(regexs).case_insensitive(!case_sensitive).build()?;
249
250 let mut n_threads = threads.unwrap_or(0);
251 if n_threads == 0 {
252 n_threads = std::thread::available_parallelism().map_or(1, |n| n.get());
253 }
254 if cfg!(test) {
255 n_threads = n_threads.min(2);
256 }
257
258 let mut salt = B256::ZERO;
259 let remaining = if let Some(caller_address) = caller {
260 salt[..20].copy_from_slice(&caller_address.into_array());
261 &mut salt[20..]
262 } else {
263 &mut salt[..]
264 };
265
266 if !no_random {
267 let mut rng = match seed {
268 Some(seed) => StdRng::from_seed(seed.0),
269 None => StdRng::from_os_rng(),
270 };
271 rng.fill_bytes(remaining);
272 }
273
274 sh_status!("Configuration:")?;
275 sh_status!("Init code hash: {init_code_hash}")?;
276 sh_status!("Regex patterns: {:?}\n", regex.patterns())?;
277 sh_status!(
278 "Starting to generate deterministic contract address with {n_threads} threads..."
279 )?;
280 let timer = Instant::now();
281 let regex_len = regex.patterns().len();
282 let mut checksum_buf = [0u8; 42];
283 let mut hex_buf = [0u8; 40];
284 let (address, salt) = super::miner::mine_salt(salt, n_threads, move |salt| {
285 #[expect(clippy::needless_borrows_for_generic_args)]
286 let addr = deployer.create2(&salt, &init_code_hash);
287 let s = if case_sensitive {
290 let _ = addr.to_checksum_raw(&mut checksum_buf, None);
291 unsafe { std::str::from_utf8_unchecked(checksum_buf.get_unchecked(2..)) }
293 } else {
294 let _ = hex::encode_to_slice(addr.as_slice(), &mut hex_buf);
296 unsafe { std::str::from_utf8_unchecked(&hex_buf) }
297 };
298 (regex.matches(s).into_iter().count() == regex_len).then_some((addr, salt))
299 })
300 .ok_or_else(|| eyre::eyre!("create2 salt mining failed: all threads panicked"))?;
301 sh_status!("Successfully found contract address in {:?}", timer.elapsed())?;
302 sh_status!("Address: {address}")?;
303 sh_status!("Salt: {salt} ({})", U256::from_be_bytes(salt.0))?;
304 if !shell::is_out_tty() {
307 sh_println!("{address}\t{salt}")?;
308 }
309
310 Ok(Create2Output { address, salt })
311 }
312}
313
314fn get_regex_hex_string(s: String) -> Result<String> {
315 let s = s.strip_prefix("0x").unwrap_or(&s);
316 let pad_width = s.len() + s.len() % 2;
317 hex::decode(format!("{s:0<pad_width$}"))?;
318 Ok(s.to_string())
319}
320
321#[cfg(test)]
322mod tests {
323 use super::*;
324 use alloy_primitives::{address, b256};
325 use std::str::FromStr;
326
327 #[test]
328 fn create2_subcommand_is_rejected_by_legacy_runner() {
329 let args = Create2Args::parse_from(["foundry-cli", "init-code-hash", "MissingContract"]);
330 let err = args.run().err().unwrap();
331 assert_eq!(
332 err.to_string(),
333 "`Create2Args::run` does not support subcommands; use `Create2Args::execute` instead"
334 );
335 }
336
337 #[test]
338 fn basic_create2() {
339 let mk_args = |args: &[&str]| {
340 Create2Args::parse_from(["foundry-cli", "--init-code-hash=0x0000000000000000000000000000000000000000000000000000000000000000"].iter().chain(args))
341 };
342
343 let args = mk_args(&["--starts-with", "aa"]);
345 let create2_out = args.run().unwrap();
346 assert!(format!("{:x}", create2_out.address).starts_with("aa"));
347
348 let args = mk_args(&["--ends-with", "bb"]);
349 let create2_out = args.run().unwrap();
350 assert!(format!("{:x}", create2_out.address).ends_with("bb"));
351
352 let args = mk_args(&["--starts-with", "aaa"]);
354 let create2_out = args.run().unwrap();
355 assert!(format!("{:x}", create2_out.address).starts_with("aaa"));
356
357 let args = mk_args(&["--ends-with", "bbb"]);
358 let create2_out = args.run().unwrap();
359 assert!(format!("{:x}", create2_out.address).ends_with("bbb"));
360
361 let args = mk_args(&["--starts-with", "0xaa"]);
363 let create2_out = args.run().unwrap();
364 assert!(format!("{:x}", create2_out.address).starts_with("aa"));
365
366 let args = mk_args(&["--starts-with", "0xaaa"]);
368 let create2_out = args.run().unwrap();
369 assert!(format!("{:x}", create2_out.address).starts_with("aaa"));
370
371 let args = mk_args(&["--starts-with", "0xerr"]);
373 let create2_out = args.run();
374 assert!(create2_out.is_err());
375
376 let args = mk_args(&["--starts-with", "x00"]);
378 let create2_out = args.run();
379 assert!(create2_out.is_err());
380 }
381
382 #[test]
383 fn matches_pattern() {
384 let args = Create2Args::parse_from([
385 "foundry-cli",
386 "--init-code-hash=0x0000000000000000000000000000000000000000000000000000000000000000",
387 "--matching=0xbbXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
388 ]);
389 let create2_out = args.run().unwrap();
390 let address = create2_out.address;
391 assert!(format!("{address:x}").starts_with("bb"));
392 }
393
394 #[test]
395 fn create2_salt() {
396 let args = Create2Args::parse_from([
397 "foundry-cli",
398 "--deployer=0x8ba1f109551bD432803012645Ac136ddd64DBA72",
399 "--salt=0x7c5ea36004851c764c44143b1dcb59679b11c9a68e5f41497f6cf3d480715331",
400 "--init-code=0x6394198df16000526103ff60206004601c335afa6040516060f3",
401 ]);
402 let create2_out = args.run().unwrap();
403 let address = create2_out.address;
404 assert_eq!(address, address!("0x533AE9D683B10C02EBDB05471642F85230071FC3"));
405 }
406
407 #[test]
408 fn create2_init_code() {
409 let init_code = "00";
410 let args =
411 Create2Args::parse_from(["foundry-cli", "--starts-with=cc", "--init-code", init_code]);
412 let create2_out = args.run().unwrap();
413 let address = create2_out.address;
414 assert!(format!("{address:x}").starts_with("cc"));
415 let salt = create2_out.salt;
416 let deployer = Address::from_str(DEPLOYER).unwrap();
417 assert_eq!(address, deployer.create2_from_code(salt, hex::decode(init_code).unwrap()));
418 }
419
420 #[test]
421 fn create2_init_code_hash() {
422 let init_code_hash = "bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a";
423 let args = Create2Args::parse_from([
424 "foundry-cli",
425 "--starts-with=dd",
426 "--init-code-hash",
427 init_code_hash,
428 ]);
429 let create2_out = args.run().unwrap();
430 let address = create2_out.address;
431 assert!(format!("{address:x}").starts_with("dd"));
432
433 let salt = create2_out.salt;
434 let deployer = Address::from_str(DEPLOYER).unwrap();
435
436 assert_eq!(
437 address,
438 deployer
439 .create2(salt, B256::from_slice(hex::decode(init_code_hash).unwrap().as_slice()))
440 );
441 }
442
443 #[test]
444 fn create2_caller() {
445 let init_code_hash = "bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a";
446 let args = Create2Args::parse_from([
447 "foundry-cli",
448 "--starts-with=dd",
449 "--init-code-hash",
450 init_code_hash,
451 "--caller=0x66f9664f97F2b50F62D13eA064982f936dE76657",
452 ]);
453 let create2_out = args.run().unwrap();
454 let address = create2_out.address;
455 let salt = create2_out.salt;
456 assert!(format!("{address:x}").starts_with("dd"));
457 assert!(format!("{salt:x}").starts_with("66f9664f97f2b50f62d13ea064982f936de76657"));
458 }
459
460 #[test]
461 fn deterministic_seed() {
462 let args = Create2Args::parse_from([
463 "foundry-cli",
464 "--starts-with=0x00",
465 "--init-code-hash=0x479d7e8f31234e208d704ba1a123c76385cea8a6981fd675b784fbd9cffb918d",
466 "--seed=0x479d7e8f31234e208d704ba1a123c76385cea8a6981fd675b784fbd9cffb918d",
467 "-j1",
468 ]);
469 let out = args.run().unwrap();
470 assert_eq!(out.address, address!("0x00614b3D65ac4a09A376a264fE1aE5E5E12A6C43"));
471 assert_eq!(
472 out.salt,
473 b256!("0x322113f523203e2c0eb00bbc8e69208b0eb0c8dad0eaac7b01d64ff016edb40d"),
474 );
475 }
476
477 #[test]
478 fn deterministic_output() {
479 let args = Create2Args::parse_from([
480 "foundry-cli",
481 "--starts-with=0x00",
482 "--init-code-hash=0x479d7e8f31234e208d704ba1a123c76385cea8a6981fd675b784fbd9cffb918d",
483 "--no-random",
484 "-j1",
485 ]);
486 let out = args.run().unwrap();
487 assert_eq!(out.address, address!("0x00bF495b8b42fdFeb91c8bCEB42CA4eE7186AEd2"));
488 assert_eq!(
489 out.salt,
490 b256!("0x000000000000000000000000000000000000000000000000df00000000000000"),
491 );
492 }
493
494 #[test]
495 fn j0() {
496 let args = Create2Args::try_parse_from([
497 "foundry-cli",
498 "--starts-with=00",
499 "--init-code-hash",
500 &B256::ZERO.to_string(),
501 "-j0",
502 ])
503 .unwrap();
504 assert_eq!(args.threads, Some(0));
505 }
506}