Skip to main content

cast/cmd/
create2.rs

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
16// https://etherscan.io/address/0x4e59b44847b379578588920ca78fbf26c0b4956c#code
17const DEPLOYER: &str = "0x4e59b44847b379578588920ca78fbf26c0b4956c";
18
19#[derive(Clone, Debug, Subcommand)]
20enum Create2Subcommand {
21    /// Compute a contract's CREATE2 init code hash.
22    #[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    /// The contract identifier in the form `<path>:<contractname>`.
31    contract: ContractInfo,
32
33    /// The constructor arguments.
34    #[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(&params)?);
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/// CLI arguments for `cast create2`.
82#[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    /// Prefix for the contract address.
89    #[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    /// Suffix for the contract address.
98    #[arg(long, short, value_name = "HEX")]
99    ends_with: Option<String>,
100
101    /// Sequence that the address has to match.
102    #[arg(long, short, value_name = "HEX")]
103    matching: Option<String>,
104
105    /// Case sensitive matching.
106    #[arg(short, long)]
107    case_sensitive: bool,
108
109    /// Address of the contract deployer.
110    #[arg(
111        short,
112        long,
113        default_value = DEPLOYER,
114        value_name = "ADDRESS"
115    )]
116    deployer: Address,
117
118    /// Salt to be used for the contract deployment. This option separate from the default salt
119    /// mining with filters.
120    #[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    /// Init code of the contract to be deployed.
136    #[arg(short, long, value_name = "HEX")]
137    init_code: Option<String>,
138
139    /// Init code hash of the contract to be deployed.
140    #[arg(alias = "ch", long, value_name = "HASH", required_unless_present = "init_code")]
141    init_code_hash: Option<String>,
142
143    /// Number of threads to use. Specifying 0 defaults to the number of logical cores.
144    #[arg(global = true, long, short = 'j', visible_alias = "jobs")]
145    threads: Option<usize>,
146
147    /// Address of the caller. Used for the first 20 bytes of the salt.
148    #[arg(long, value_name = "ADDRESS")]
149    caller: Option<Address>,
150
151    /// The random number generator's seed, used to initialize the salt.
152    #[arg(long, value_name = "HEX")]
153    seed: Option<B256>,
154
155    /// Don't initialize the salt with a random value, and instead use the default value of 0.
156    #[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            // replacing X placeholders by . to match any character at these positions
226
227            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            // Use checksum format only when case_sensitive is enabled — it requires an extra
288            // keccak256 call, so we fall back to plain hex when case sensitivity is off.
289            let s = if case_sensitive {
290                let _ = addr.to_checksum_raw(&mut checksum_buf, None);
291                // SAFETY: stripping 2 ASCII bytes ("0x") off of an already valid UTF-8 string.
292                unsafe { std::str::from_utf8_unchecked(checksum_buf.get_unchecked(2..)) }
293            } else {
294                // SAFETY: hex::encode_to_slice always produces valid UTF-8 (hex digits).
295                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        // The machine-readable stdout record duplicates the prose above when stdout is an
305        // interactive terminal.
306        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        // even hex chars
344        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        // odd hex chars
353        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        // even hex chars with 0x prefix
362        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        // odd hex chars with 0x prefix
367        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        // check fails on wrong chars
372        let args = mk_args(&["--starts-with", "0xerr"]);
373        let create2_out = args.run();
374        assert!(create2_out.is_err());
375
376        // check fails on wrong x prefixed string provided
377        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}