Skip to main content

cast/cmd/
create2.rs

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::{
7    sync::{
8        Arc,
9        atomic::{AtomicBool, Ordering},
10    },
11    time::Instant,
12};
13
14// https://etherscan.io/address/0x4e59b44847b379578588920ca78fbf26c0b4956c#code
15const DEPLOYER: &str = "0x4e59b44847b379578588920ca78fbf26c0b4956c";
16
17/// CLI arguments for `cast create2`.
18#[derive(Clone, Debug, Parser)]
19pub struct Create2Args {
20    /// Prefix for the contract address.
21    #[arg(
22        long,
23        short,
24        required_unless_present_any = &["ends_with", "matching", "salt"],
25        value_name = "HEX"
26    )]
27    starts_with: Option<String>,
28
29    /// Suffix for the contract address.
30    #[arg(long, short, value_name = "HEX")]
31    ends_with: Option<String>,
32
33    /// Sequence that the address has to match.
34    #[arg(long, short, value_name = "HEX")]
35    matching: Option<String>,
36
37    /// Case sensitive matching.
38    #[arg(short, long)]
39    case_sensitive: bool,
40
41    /// Address of the contract deployer.
42    #[arg(
43        short,
44        long,
45        default_value = DEPLOYER,
46        value_name = "ADDRESS"
47    )]
48    deployer: Address,
49
50    /// Salt to be used for the contract deployment. This option separate from the default salt
51    /// mining with filters.
52    #[arg(
53        long,
54        conflicts_with_all = [
55            "starts_with",
56            "ends_with",
57            "matching",
58            "case_sensitive",
59            "caller",
60            "seed",
61            "no_random"
62        ],
63        value_name = "HEX"
64    )]
65    salt: Option<String>,
66
67    /// Init code of the contract to be deployed.
68    #[arg(short, long, value_name = "HEX")]
69    init_code: Option<String>,
70
71    /// Init code hash of the contract to be deployed.
72    #[arg(alias = "ch", long, value_name = "HASH", required_unless_present = "init_code")]
73    init_code_hash: Option<String>,
74
75    /// Number of threads to use. Specifying 0 defaults to the number of logical cores.
76    #[arg(global = true, long, short = 'j', visible_alias = "jobs")]
77    threads: Option<usize>,
78
79    /// Address of the caller. Used for the first 20 bytes of the salt.
80    #[arg(long, value_name = "ADDRESS")]
81    caller: Option<Address>,
82
83    /// The random number generator's seed, used to initialize the salt.
84    #[arg(long, value_name = "HEX")]
85    seed: Option<B256>,
86
87    /// Don't initialize the salt with a random value, and instead use the default value of 0.
88    #[arg(long, conflicts_with = "seed")]
89    no_random: bool,
90}
91
92pub struct Create2Output {
93    pub address: Address,
94    pub salt: B256,
95}
96
97impl Create2Args {
98    pub fn run(self) -> Result<Create2Output> {
99        let Self {
100            starts_with,
101            ends_with,
102            matching,
103            case_sensitive,
104            deployer,
105            salt,
106            init_code,
107            init_code_hash,
108            threads,
109            caller,
110            seed,
111            no_random,
112        } = self;
113
114        let init_code_hash = if let Some(init_code_hash) = init_code_hash {
115            hex::FromHex::from_hex(init_code_hash)
116        } else if let Some(init_code) = init_code {
117            hex::decode(init_code).map(keccak256)
118        } else {
119            unreachable!();
120        }?;
121
122        if let Some(salt) = salt {
123            let salt = hex::FromHex::from_hex(salt)?;
124            let address = deployer.create2(salt, init_code_hash);
125            sh_println!("{address}")?;
126            return Ok(Create2Output { address, salt });
127        }
128
129        let mut regexs = vec![];
130
131        if let Some(matches) = matching {
132            if starts_with.is_some() || ends_with.is_some() {
133                eyre::bail!("Either use --matching or --starts/ends-with");
134            }
135
136            let matches = matches.trim_start_matches("0x");
137
138            if matches.len() != 40 {
139                eyre::bail!("Please provide a 40 characters long sequence for matching");
140            }
141
142            hex::decode(matches.replace('X', "0")).wrap_err("invalid matching hex provided")?;
143            // replacing X placeholders by . to match any character at these positions
144
145            regexs.push(matches.replace('X', "."));
146        }
147
148        if let Some(prefix) = starts_with {
149            regexs.push(format!(
150                r"^{}",
151                get_regex_hex_string(prefix).wrap_err("invalid prefix hex provided")?
152            ));
153        }
154        if let Some(suffix) = ends_with {
155            regexs.push(format!(
156                r"{}$",
157                get_regex_hex_string(suffix).wrap_err("invalid suffix hex provided")?
158            ))
159        }
160
161        debug_assert!(
162            regexs.iter().map(|p| p.len() - 1).sum::<usize>() <= 40,
163            "vanity patterns length exceeded. cannot be more than 40 characters",
164        );
165
166        let regex = RegexSetBuilder::new(regexs).case_insensitive(!case_sensitive).build()?;
167
168        let mut n_threads = threads.unwrap_or(0);
169        if n_threads == 0 {
170            n_threads = std::thread::available_parallelism().map_or(1, |n| n.get());
171        }
172        if cfg!(test) {
173            n_threads = n_threads.min(2);
174        }
175
176        let mut salt = B256::ZERO;
177        let remaining = if let Some(caller_address) = caller {
178            salt[..20].copy_from_slice(&caller_address.into_array());
179            &mut salt[20..]
180        } else {
181            &mut salt[..]
182        };
183
184        if !no_random {
185            let mut rng = match seed {
186                Some(seed) => StdRng::from_seed(seed.0),
187                None => StdRng::from_os_rng(),
188            };
189            rng.fill_bytes(remaining);
190        }
191
192        sh_println!("Configuration:")?;
193        sh_println!("Init code hash: {init_code_hash}")?;
194        sh_println!("Regex patterns: {:?}\n", regex.patterns())?;
195        sh_println!(
196            "Starting to generate deterministic contract address with {n_threads} threads..."
197        )?;
198        let mut handles = Vec::with_capacity(n_threads);
199        let found = Arc::new(AtomicBool::new(false));
200        let timer = Instant::now();
201
202        // Loops through all possible salts in parallel until a result is found.
203        // Each thread iterates over `(i..).step_by(n_threads)`.
204        for i in 0..n_threads {
205            // Create local copies for the thread.
206            let increment = n_threads;
207            let regex = regex.clone();
208            let regex_len = regex.patterns().len();
209            let found = Arc::clone(&found);
210            handles.push(std::thread::spawn(move || {
211                // Read the first bytes of the salt as a usize to be able to increment it.
212                struct B256Aligned(B256, [usize; 0]);
213                let mut salt = B256Aligned(salt, []);
214                // SAFETY: B256 is aligned to `usize`.
215                let salt_word = unsafe {
216                    &mut *salt.0.as_mut_ptr().add(32 - usize::BITS as usize / 8).cast::<usize>()
217                };
218                // Important: add the thread index to the salt to avoid duplicate results.
219                *salt_word = salt_word.wrapping_add(i);
220
221                // Use checksum format only when case_sensitive is enabled.
222                // This avoids an extra keccak256 call per iteration when not needed.
223                let mut checksum_buf = [0u8; 42];
224                let mut hex_buf = [0u8; 40];
225                loop {
226                    // Stop if a result was found in another thread.
227                    if found.load(Ordering::Relaxed) {
228                        break None;
229                    }
230
231                    // Calculate the `CREATE2` address.
232                    #[expect(clippy::needless_borrows_for_generic_args)]
233                    let addr = deployer.create2(&salt.0, &init_code_hash);
234
235                    // Check if the regex matches the calculated address.
236                    // When case_sensitive is true, use EIP-55 checksum format (requires keccak256).
237                    // Otherwise, use lowercase hex to avoid the extra hash computation.
238                    let s = if case_sensitive {
239                        let _ = addr.to_checksum_raw(&mut checksum_buf, None);
240                        // SAFETY: stripping 2 ASCII bytes ("0x") off of an already valid UTF-8
241                        // string is safe.
242                        unsafe { std::str::from_utf8_unchecked(checksum_buf.get_unchecked(2..)) }
243                    } else {
244                        // SAFETY: hex::encode_to_slice always produces valid UTF-8 (hex digits).
245                        let _ = hex::encode_to_slice(addr.as_slice(), &mut hex_buf);
246                        unsafe { std::str::from_utf8_unchecked(&hex_buf) }
247                    };
248                    if regex.matches(s).into_iter().count() == regex_len {
249                        // Notify other threads that we found a result.
250                        found.store(true, Ordering::Relaxed);
251                        break Some((addr, salt.0));
252                    }
253
254                    // Increment the salt for the next iteration.
255                    *salt_word = salt_word.wrapping_add(increment);
256                }
257            }));
258        }
259
260        let results = handles.into_iter().filter_map(|h| h.join().unwrap()).collect::<Vec<_>>();
261        let (address, salt) = results.into_iter().next().unwrap();
262        sh_println!("Successfully found contract address in {:?}", timer.elapsed())?;
263        sh_println!("Address: {address}")?;
264        sh_println!("Salt: {salt} ({})", U256::from_be_bytes(salt.0))?;
265
266        Ok(Create2Output { address, salt })
267    }
268}
269
270fn get_regex_hex_string(s: String) -> Result<String> {
271    let s = s.strip_prefix("0x").unwrap_or(&s);
272    let pad_width = s.len() + s.len() % 2;
273    hex::decode(format!("{s:0<pad_width$}"))?;
274    Ok(s.to_string())
275}
276
277#[cfg(test)]
278mod tests {
279    use super::*;
280    use alloy_primitives::{address, b256};
281    use std::str::FromStr;
282
283    #[test]
284    fn basic_create2() {
285        let mk_args = |args: &[&str]| {
286            Create2Args::parse_from(["foundry-cli", "--init-code-hash=0x0000000000000000000000000000000000000000000000000000000000000000"].iter().chain(args))
287        };
288
289        // even hex chars
290        let args = mk_args(&["--starts-with", "aa"]);
291        let create2_out = args.run().unwrap();
292        assert!(format!("{:x}", create2_out.address).starts_with("aa"));
293
294        let args = mk_args(&["--ends-with", "bb"]);
295        let create2_out = args.run().unwrap();
296        assert!(format!("{:x}", create2_out.address).ends_with("bb"));
297
298        // odd hex chars
299        let args = mk_args(&["--starts-with", "aaa"]);
300        let create2_out = args.run().unwrap();
301        assert!(format!("{:x}", create2_out.address).starts_with("aaa"));
302
303        let args = mk_args(&["--ends-with", "bbb"]);
304        let create2_out = args.run().unwrap();
305        assert!(format!("{:x}", create2_out.address).ends_with("bbb"));
306
307        // even hex chars with 0x prefix
308        let args = mk_args(&["--starts-with", "0xaa"]);
309        let create2_out = args.run().unwrap();
310        assert!(format!("{:x}", create2_out.address).starts_with("aa"));
311
312        // odd hex chars with 0x prefix
313        let args = mk_args(&["--starts-with", "0xaaa"]);
314        let create2_out = args.run().unwrap();
315        assert!(format!("{:x}", create2_out.address).starts_with("aaa"));
316
317        // check fails on wrong chars
318        let args = mk_args(&["--starts-with", "0xerr"]);
319        let create2_out = args.run();
320        assert!(create2_out.is_err());
321
322        // check fails on wrong x prefixed string provided
323        let args = mk_args(&["--starts-with", "x00"]);
324        let create2_out = args.run();
325        assert!(create2_out.is_err());
326    }
327
328    #[test]
329    fn matches_pattern() {
330        let args = Create2Args::parse_from([
331            "foundry-cli",
332            "--init-code-hash=0x0000000000000000000000000000000000000000000000000000000000000000",
333            "--matching=0xbbXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
334        ]);
335        let create2_out = args.run().unwrap();
336        let address = create2_out.address;
337        assert!(format!("{address:x}").starts_with("bb"));
338    }
339
340    #[test]
341    fn create2_salt() {
342        let args = Create2Args::parse_from([
343            "foundry-cli",
344            "--deployer=0x8ba1f109551bD432803012645Ac136ddd64DBA72",
345            "--salt=0x7c5ea36004851c764c44143b1dcb59679b11c9a68e5f41497f6cf3d480715331",
346            "--init-code=0x6394198df16000526103ff60206004601c335afa6040516060f3",
347        ]);
348        let create2_out = args.run().unwrap();
349        let address = create2_out.address;
350        assert_eq!(address, address!("0x533AE9D683B10C02EBDB05471642F85230071FC3"));
351    }
352
353    #[test]
354    fn create2_init_code() {
355        let init_code = "00";
356        let args =
357            Create2Args::parse_from(["foundry-cli", "--starts-with=cc", "--init-code", init_code]);
358        let create2_out = args.run().unwrap();
359        let address = create2_out.address;
360        assert!(format!("{address:x}").starts_with("cc"));
361        let salt = create2_out.salt;
362        let deployer = Address::from_str(DEPLOYER).unwrap();
363        assert_eq!(address, deployer.create2_from_code(salt, hex::decode(init_code).unwrap()));
364    }
365
366    #[test]
367    fn create2_init_code_hash() {
368        let init_code_hash = "bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a";
369        let args = Create2Args::parse_from([
370            "foundry-cli",
371            "--starts-with=dd",
372            "--init-code-hash",
373            init_code_hash,
374        ]);
375        let create2_out = args.run().unwrap();
376        let address = create2_out.address;
377        assert!(format!("{address:x}").starts_with("dd"));
378
379        let salt = create2_out.salt;
380        let deployer = Address::from_str(DEPLOYER).unwrap();
381
382        assert_eq!(
383            address,
384            deployer
385                .create2(salt, B256::from_slice(hex::decode(init_code_hash).unwrap().as_slice()))
386        );
387    }
388
389    #[test]
390    fn create2_caller() {
391        let init_code_hash = "bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a";
392        let args = Create2Args::parse_from([
393            "foundry-cli",
394            "--starts-with=dd",
395            "--init-code-hash",
396            init_code_hash,
397            "--caller=0x66f9664f97F2b50F62D13eA064982f936dE76657",
398        ]);
399        let create2_out = args.run().unwrap();
400        let address = create2_out.address;
401        let salt = create2_out.salt;
402        assert!(format!("{address:x}").starts_with("dd"));
403        assert!(format!("{salt:x}").starts_with("66f9664f97f2b50f62d13ea064982f936de76657"));
404    }
405
406    #[test]
407    fn deterministic_seed() {
408        let args = Create2Args::parse_from([
409            "foundry-cli",
410            "--starts-with=0x00",
411            "--init-code-hash=0x479d7e8f31234e208d704ba1a123c76385cea8a6981fd675b784fbd9cffb918d",
412            "--seed=0x479d7e8f31234e208d704ba1a123c76385cea8a6981fd675b784fbd9cffb918d",
413            "-j1",
414        ]);
415        let out = args.run().unwrap();
416        assert_eq!(out.address, address!("0x00614b3D65ac4a09A376a264fE1aE5E5E12A6C43"));
417        assert_eq!(
418            out.salt,
419            b256!("0x322113f523203e2c0eb00bbc8e69208b0eb0c8dad0eaac7b01d64ff016edb40d"),
420        );
421    }
422
423    #[test]
424    fn deterministic_output() {
425        let args = Create2Args::parse_from([
426            "foundry-cli",
427            "--starts-with=0x00",
428            "--init-code-hash=0x479d7e8f31234e208d704ba1a123c76385cea8a6981fd675b784fbd9cffb918d",
429            "--no-random",
430            "-j1",
431        ]);
432        let out = args.run().unwrap();
433        assert_eq!(out.address, address!("0x00bF495b8b42fdFeb91c8bCEB42CA4eE7186AEd2"));
434        assert_eq!(
435            out.salt,
436            b256!("0x000000000000000000000000000000000000000000000000df00000000000000"),
437        );
438    }
439
440    #[test]
441    fn j0() {
442        let args = Create2Args::try_parse_from([
443            "foundry-cli",
444            "--starts-with=00",
445            "--init-code-hash",
446            &B256::ZERO.to_string(),
447            "-j0",
448        ])
449        .unwrap();
450        assert_eq!(args.threads, Some(0));
451    }
452}