Skip to main content

cast/cmd/
create2.rs

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
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 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(&params)?);
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/// CLI arguments for `cast create2`.
74#[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    /// Prefix for the contract address.
81    #[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    /// Suffix for the contract address.
90    #[arg(long, short, value_name = "HEX")]
91    ends_with: Option<String>,
92
93    /// Sequence that the address has to match.
94    #[arg(long, short, value_name = "HEX")]
95    matching: Option<String>,
96
97    /// Case sensitive matching.
98    #[arg(short, long)]
99    case_sensitive: bool,
100
101    /// Address of the contract deployer.
102    #[arg(
103        short,
104        long,
105        default_value = DEPLOYER,
106        value_name = "ADDRESS"
107    )]
108    deployer: Address,
109
110    /// Salt to be used for the contract deployment. This option separate from the default salt
111    /// mining with filters.
112    #[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    /// Init code of the contract to be deployed.
128    #[arg(short, long, value_name = "HEX")]
129    init_code: Option<String>,
130
131    /// Init code hash of the contract to be deployed.
132    #[arg(alias = "ch", long, value_name = "HASH", required_unless_present = "init_code")]
133    init_code_hash: Option<String>,
134
135    /// Number of threads to use. Specifying 0 defaults to the number of logical cores.
136    #[arg(global = true, long, short = 'j', visible_alias = "jobs")]
137    threads: Option<usize>,
138
139    /// Address of the caller. Used for the first 20 bytes of the salt.
140    #[arg(long, value_name = "ADDRESS")]
141    caller: Option<Address>,
142
143    /// The random number generator's seed, used to initialize the salt.
144    #[arg(long, value_name = "HEX")]
145    seed: Option<B256>,
146
147    /// Don't initialize the salt with a random value, and instead use the default value of 0.
148    #[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    /// Mines (or derives) the salt and returns the resulting address and salt.
161    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            // Clap requires one of the two.
181            (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            // replacing X placeholders by . to match any character at these positions
206
207            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            // Use checksum format only when case_sensitive is enabled — it requires an extra
268            // keccak256 call, so we fall back to plain hex when case sensitivity is off.
269            let s = if case_sensitive {
270                let _ = addr.to_checksum_raw(&mut checksum_buf, None);
271                // SAFETY: stripping 2 ASCII bytes ("0x") off of an already valid UTF-8 string.
272                unsafe { std::str::from_utf8_unchecked(checksum_buf.get_unchecked(2..)) }
273            } else {
274                // SAFETY: hex::encode_to_slice always produces valid UTF-8 (hex digits).
275                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        // The machine-readable stdout record duplicates the prose above when stdout is an
285        // interactive terminal.
286        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        // Non-hex and misplaced prefixes are rejected.
338        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}