1use alloy_dyn_abi::JsonAbiExt;
2use alloy_primitives::{Address, B256, U256, hex, hex::FromHex, 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 InitCodeHashArgs {
42 fn run(&self) -> Result<()> {
43 let config = self.load_config()?;
44 let project = config.project()?;
45 let target_path = if let Some(path) = &self.contract.path {
46 canonicalize(project.root().join(path))?
47 } else {
48 project.find_contract_path(&self.contract.name)?
49 };
50
51 let output = compile::compile_target(&target_path, &project, true)?;
52 let (abi, bin, _) = find_contract_artifacts(output, &target_path, &self.contract.name)?;
53 let Some(bytecode) = bin.object.into_bytes() else {
54 eyre::bail!("contract contains unlinked libraries");
55 };
56 if bytecode.is_empty() {
57 eyre::bail!("no bytecode found in bin object for {}", self.contract.name);
58 }
59
60 let mut init_code = bytecode.to_vec();
61 if let Some(constructor) = &abi.constructor {
62 let params = parse_constructor_args(constructor, &self.constructor_args)?;
63 init_code.extend(constructor.abi_encode_input(¶ms)?);
64 } else if !self.constructor_args.is_empty() {
65 eyre::bail!("contract does not have a constructor");
66 }
67
68 print_scalar(keccak256(init_code))?;
69 Ok(())
70 }
71}
72
73#[derive(Clone, Debug, Parser)]
75#[command(subcommand_negates_reqs = true, args_conflicts_with_subcommands = true)]
76pub struct Create2Args {
77 #[command(subcommand)]
78 command: Option<Create2Subcommand>,
79
80 #[arg(
82 long,
83 short,
84 required_unless_present_any = &["ends_with", "matching", "salt"],
85 value_name = "HEX"
86 )]
87 starts_with: Option<String>,
88
89 #[arg(long, short, value_name = "HEX")]
91 ends_with: Option<String>,
92
93 #[arg(long, short, value_name = "HEX")]
95 matching: Option<String>,
96
97 #[arg(short, long)]
99 case_sensitive: bool,
100
101 #[arg(
103 short,
104 long,
105 default_value = DEPLOYER,
106 value_name = "ADDRESS"
107 )]
108 deployer: Address,
109
110 #[arg(
113 long,
114 conflicts_with_all = [
115 "starts_with",
116 "ends_with",
117 "matching",
118 "case_sensitive",
119 "caller",
120 "seed",
121 "no_random"
122 ],
123 value_name = "HEX"
124 )]
125 salt: Option<String>,
126
127 #[arg(short, long, value_name = "HEX")]
129 init_code: Option<String>,
130
131 #[arg(alias = "ch", long, value_name = "HASH", required_unless_present = "init_code")]
133 init_code_hash: Option<String>,
134
135 #[arg(global = true, long, short = 'j', visible_alias = "jobs")]
137 threads: Option<usize>,
138
139 #[arg(long, value_name = "ADDRESS")]
141 caller: Option<Address>,
142
143 #[arg(long, value_name = "HEX")]
145 seed: Option<B256>,
146
147 #[arg(long, conflicts_with = "seed")]
149 no_random: bool,
150}
151
152impl Create2Args {
153 pub fn execute(self) -> Result<()> {
154 if let Some(Create2Subcommand::InitCodeHash(args)) = &self.command {
155 return args.run();
156 }
157 self.run().map(drop)
158 }
159
160 fn run(self) -> Result<(Address, B256)> {
162 let Self {
163 command: _,
164 starts_with,
165 ends_with,
166 matching,
167 case_sensitive,
168 deployer,
169 salt,
170 init_code,
171 init_code_hash,
172 threads,
173 caller,
174 seed,
175 no_random,
176 } = self;
177
178 let init_code_hash = match (init_code_hash, init_code) {
179 (Some(init_code_hash), _) => B256::from_hex(init_code_hash)?,
180 (None, init_code) => keccak256(hex::decode(init_code.unwrap_or_default())?),
182 };
183
184 if let Some(salt) = salt {
185 let salt = B256::from_hex(salt)?;
186 let address = deployer.create2(salt, init_code_hash);
187 sh_println!("{address}\t{salt}")?;
188 return Ok((address, salt));
189 }
190
191 let mut regexs = vec![];
192
193 if let Some(matches) = matching {
194 if starts_with.is_some() || ends_with.is_some() {
195 eyre::bail!("Either use --matching or --starts/ends-with");
196 }
197
198 let matches = matches.trim_start_matches("0x");
199
200 if matches.len() != 40 {
201 eyre::bail!("Please provide a 40 characters long sequence for matching");
202 }
203
204 hex::decode(matches.replace('X', "0")).wrap_err("invalid matching hex provided")?;
205 regexs.push(matches.replace('X', "."));
208 }
209
210 if let Some(prefix) = starts_with {
211 regexs.push(format!(
212 r"^{}",
213 get_regex_hex_string(prefix).wrap_err("invalid prefix hex provided")?
214 ));
215 }
216 if let Some(suffix) = ends_with {
217 regexs.push(format!(
218 r"{}$",
219 get_regex_hex_string(suffix).wrap_err("invalid suffix hex provided")?
220 ))
221 }
222
223 debug_assert!(
224 regexs.iter().map(|p| p.len() - 1).sum::<usize>() <= 40,
225 "vanity patterns length exceeded. cannot be more than 40 characters",
226 );
227
228 let regex = RegexSetBuilder::new(regexs).case_insensitive(!case_sensitive).build()?;
229
230 let mut n_threads = match threads {
231 Some(n) if n != 0 => n,
232 _ => std::thread::available_parallelism().map_or(1, |n| n.get()),
233 };
234 if cfg!(test) {
235 n_threads = n_threads.min(2);
236 }
237
238 let mut salt = B256::ZERO;
239 let remaining = if let Some(caller_address) = caller {
240 salt[..20].copy_from_slice(&caller_address.into_array());
241 &mut salt[20..]
242 } else {
243 &mut salt[..]
244 };
245
246 if !no_random {
247 let mut rng = match seed {
248 Some(seed) => StdRng::from_seed(seed.0),
249 None => StdRng::from_os_rng(),
250 };
251 rng.fill_bytes(remaining);
252 }
253
254 sh_status!("Configuration:")?;
255 sh_status!("Init code hash: {init_code_hash}")?;
256 sh_status!("Regex patterns: {:?}\n", regex.patterns())?;
257 sh_status!(
258 "Starting to generate deterministic contract address with {n_threads} threads..."
259 )?;
260 let timer = Instant::now();
261 let regex_len = regex.patterns().len();
262 let mut checksum_buf = [0u8; 42];
263 let mut hex_buf = [0u8; 40];
264 let (address, salt) = super::miner::mine_salt(salt, n_threads, move |salt| {
265 #[expect(clippy::needless_borrows_for_generic_args)]
266 let addr = deployer.create2(&salt, &init_code_hash);
267 let s = if case_sensitive {
270 let _ = addr.to_checksum_raw(&mut checksum_buf, None);
271 unsafe { std::str::from_utf8_unchecked(checksum_buf.get_unchecked(2..)) }
273 } else {
274 let _ = hex::encode_to_slice(addr.as_slice(), &mut hex_buf);
276 unsafe { std::str::from_utf8_unchecked(&hex_buf) }
277 };
278 (regex.matches(s).into_iter().count() == regex_len).then_some((addr, salt))
279 })
280 .ok_or_else(|| eyre::eyre!("create2 salt mining failed: all threads panicked"))?;
281 sh_status!("Successfully found contract address in {:?}", timer.elapsed())?;
282 sh_status!("Address: {address}")?;
283 sh_status!("Salt: {salt} ({})", U256::from_be_bytes(salt.0))?;
284 if !shell::is_out_tty() {
287 sh_println!("{address}\t{salt}")?;
288 }
289
290 Ok((address, salt))
291 }
292}
293
294fn get_regex_hex_string(s: String) -> Result<String> {
295 let s = s.strip_prefix("0x").unwrap_or(&s);
296 let pad_width = s.len() + s.len() % 2;
297 hex::decode(format!("{s:0<pad_width$}"))?;
298 Ok(s.to_string())
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304 use alloy_primitives::{address, b256};
305 use std::str::FromStr;
306
307 const ZERO_HASH: &str =
308 "--init-code-hash=0x0000000000000000000000000000000000000000000000000000000000000000";
309
310 fn run(args: &[&str]) -> Result<(Address, B256)> {
311 Create2Args::parse_from(["foundry-cli"].iter().chain(args)).run()
312 }
313
314 #[test]
315 fn basic_create2() {
316 for (flag, pattern) in [
317 ("--starts-with", "aa"),
318 ("--ends-with", "bb"),
319 ("--starts-with", "aaa"),
320 ("--ends-with", "bbb"),
321 ("--starts-with", "0xaa"),
322 ("--starts-with", "0xaaa"),
323 ] {
324 let (address, _) = run(&[ZERO_HASH, flag, pattern]).unwrap();
325 let address = format!("{address:x}");
326 let pattern = pattern.trim_start_matches("0x");
327 assert!(
328 if flag == "--starts-with" {
329 address.starts_with(pattern)
330 } else {
331 address.ends_with(pattern)
332 },
333 "{flag} {pattern}: {address}"
334 );
335 }
336
337 assert!(run(&[ZERO_HASH, "--starts-with", "0xerr"]).is_err());
339 assert!(run(&[ZERO_HASH, "--starts-with", "x00"]).is_err());
340 }
341
342 #[test]
343 fn matches_pattern() {
344 let (address, _) =
345 run(&[ZERO_HASH, "--matching=0xbbXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"]).unwrap();
346 assert!(format!("{address:x}").starts_with("bb"));
347 }
348
349 #[test]
350 fn create2_salt() {
351 let (address, _) = run(&[
352 "--deployer=0x8ba1f109551bD432803012645Ac136ddd64DBA72",
353 "--salt=0x7c5ea36004851c764c44143b1dcb59679b11c9a68e5f41497f6cf3d480715331",
354 "--init-code=0x6394198df16000526103ff60206004601c335afa6040516060f3",
355 ])
356 .unwrap();
357 assert_eq!(address, address!("0x533AE9D683B10C02EBDB05471642F85230071FC3"));
358 }
359
360 #[test]
361 fn create2_init_code() {
362 let init_code = "00";
363 let (address, salt) = run(&["--starts-with=cc", "--init-code", init_code]).unwrap();
364 assert!(format!("{address:x}").starts_with("cc"));
365 let deployer = Address::from_str(DEPLOYER).unwrap();
366 assert_eq!(address, deployer.create2_from_code(salt, hex::decode(init_code).unwrap()));
367 }
368
369 #[test]
370 fn create2_init_code_hash() {
371 let init_code_hash = "bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a";
372 let (address, salt) =
373 run(&["--starts-with=dd", "--init-code-hash", init_code_hash]).unwrap();
374 assert!(format!("{address:x}").starts_with("dd"));
375 let deployer = Address::from_str(DEPLOYER).unwrap();
376 assert_eq!(address, deployer.create2(salt, B256::from_str(init_code_hash).unwrap()));
377 }
378
379 #[test]
380 fn create2_caller() {
381 let (address, salt) = run(&[
382 "--starts-with=dd",
383 "--init-code-hash=bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a",
384 "--caller=0x66f9664f97F2b50F62D13eA064982f936dE76657",
385 ])
386 .unwrap();
387 assert!(format!("{address:x}").starts_with("dd"));
388 assert!(format!("{salt:x}").starts_with("66f9664f97f2b50f62d13ea064982f936de76657"));
389 }
390
391 #[test]
392 fn deterministic_seed() {
393 let (address, salt) = run(&[
394 "--starts-with=0x00",
395 "--init-code-hash=0x479d7e8f31234e208d704ba1a123c76385cea8a6981fd675b784fbd9cffb918d",
396 "--seed=0x479d7e8f31234e208d704ba1a123c76385cea8a6981fd675b784fbd9cffb918d",
397 "-j1",
398 ])
399 .unwrap();
400 assert_eq!(address, address!("0x00614b3D65ac4a09A376a264fE1aE5E5E12A6C43"));
401 assert_eq!(
402 salt,
403 b256!("0x322113f523203e2c0eb00bbc8e69208b0eb0c8dad0eaac7b01d64ff016edb40d")
404 );
405 }
406
407 #[test]
408 fn deterministic_output() {
409 let (address, salt) = run(&[
410 "--starts-with=0x00",
411 "--init-code-hash=0x479d7e8f31234e208d704ba1a123c76385cea8a6981fd675b784fbd9cffb918d",
412 "--no-random",
413 "-j1",
414 ])
415 .unwrap();
416 assert_eq!(address, address!("0x00bF495b8b42fdFeb91c8bCEB42CA4eE7186AEd2"));
417 assert_eq!(
418 salt,
419 b256!("0x000000000000000000000000000000000000000000000000df00000000000000")
420 );
421 }
422}