cast/cmd/
create2.rs

1use alloy_primitives::{hex, keccak256, Address, B256, U256};
2use clap::Parser;
3use eyre::{Result, WrapErr};
4use rand::{rngs::StdRng, RngCore, SeedableRng};
5use regex::RegexSetBuilder;
6use std::{
7    sync::{
8        atomic::{AtomicBool, Ordering},
9        Arc,
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 prefix 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 = std::thread::available_parallelism().map_or(1, |n| n.get());
169        if let Some(threads) = threads {
170            n_threads = n_threads.min(threads);
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_entropy(),
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                let mut checksum = [0; 42];
222                loop {
223                    // Stop if a result was found in another thread.
224                    if found.load(Ordering::Relaxed) {
225                        break None;
226                    }
227
228                    // Calculate the `CREATE2` address.
229                    #[expect(clippy::needless_borrows_for_generic_args)]
230                    let addr = deployer.create2(&salt.0, &init_code_hash);
231
232                    // Check if the regex matches the calculated address' checksum.
233                    let _ = addr.to_checksum_raw(&mut checksum, None);
234                    // SAFETY: stripping 2 ASCII bytes ("0x") off of an already valid UTF-8 string
235                    // is safe.
236                    let s = unsafe { std::str::from_utf8_unchecked(checksum.get_unchecked(2..)) };
237                    if regex.matches(s).into_iter().count() == regex_len {
238                        // Notify other threads that we found a result.
239                        found.store(true, Ordering::Relaxed);
240                        break Some((addr, salt.0));
241                    }
242
243                    // Increment the salt for the next iteration.
244                    *salt_word = salt_word.wrapping_add(increment);
245                }
246            }));
247        }
248
249        let results = handles.into_iter().filter_map(|h| h.join().unwrap()).collect::<Vec<_>>();
250        let (address, salt) = results.into_iter().next().unwrap();
251        sh_println!("Successfully found contract address in {:?}", timer.elapsed())?;
252        sh_println!("Address: {address}")?;
253        sh_println!("Salt: {salt} ({})", U256::from_be_bytes(salt.0))?;
254
255        Ok(Create2Output { address, salt })
256    }
257}
258
259fn get_regex_hex_string(s: String) -> Result<String> {
260    let s = s.strip_prefix("0x").unwrap_or(&s);
261    let pad_width = s.len() + s.len() % 2;
262    hex::decode(format!("{s:0<pad_width$}"))?;
263    Ok(s.to_string())
264}
265
266#[cfg(test)]
267mod tests {
268    use super::*;
269    use alloy_primitives::{address, b256};
270    use std::str::FromStr;
271
272    #[test]
273    fn basic_create2() {
274        let mk_args = |args: &[&str]| {
275            Create2Args::parse_from(["foundry-cli", "--init-code-hash=0x0000000000000000000000000000000000000000000000000000000000000000"].iter().chain(args))
276        };
277
278        // even hex chars
279        let args = mk_args(&["--starts-with", "aa"]);
280        let create2_out = args.run().unwrap();
281        assert!(format!("{:x}", create2_out.address).starts_with("aa"));
282
283        let args = mk_args(&["--ends-with", "bb"]);
284        let create2_out = args.run().unwrap();
285        assert!(format!("{:x}", create2_out.address).ends_with("bb"));
286
287        // odd hex chars
288        let args = mk_args(&["--starts-with", "aaa"]);
289        let create2_out = args.run().unwrap();
290        assert!(format!("{:x}", create2_out.address).starts_with("aaa"));
291
292        let args = mk_args(&["--ends-with", "bbb"]);
293        let create2_out = args.run().unwrap();
294        assert!(format!("{:x}", create2_out.address).ends_with("bbb"));
295
296        // even hex chars with 0x prefix
297        let args = mk_args(&["--starts-with", "0xaa"]);
298        let create2_out = args.run().unwrap();
299        assert!(format!("{:x}", create2_out.address).starts_with("aa"));
300
301        // odd hex chars with 0x prefix
302        let args = mk_args(&["--starts-with", "0xaaa"]);
303        let create2_out = args.run().unwrap();
304        assert!(format!("{:x}", create2_out.address).starts_with("aaa"));
305
306        // check fails on wrong chars
307        let args = mk_args(&["--starts-with", "0xerr"]);
308        let create2_out = args.run();
309        assert!(create2_out.is_err());
310
311        // check fails on wrong x prefixed string provided
312        let args = mk_args(&["--starts-with", "x00"]);
313        let create2_out = args.run();
314        assert!(create2_out.is_err());
315    }
316
317    #[test]
318    fn matches_pattern() {
319        let args = Create2Args::parse_from([
320            "foundry-cli",
321            "--init-code-hash=0x0000000000000000000000000000000000000000000000000000000000000000",
322            "--matching=0xbbXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
323        ]);
324        let create2_out = args.run().unwrap();
325        let address = create2_out.address;
326        assert!(format!("{address:x}").starts_with("bb"));
327    }
328
329    #[test]
330    fn create2_salt() {
331        let args = Create2Args::parse_from([
332            "foundry-cli",
333            "--deployer=0x8ba1f109551bD432803012645Ac136ddd64DBA72",
334            "--salt=0x7c5ea36004851c764c44143b1dcb59679b11c9a68e5f41497f6cf3d480715331",
335            "--init-code=0x6394198df16000526103ff60206004601c335afa6040516060f3",
336        ]);
337        let create2_out = args.run().unwrap();
338        let address = create2_out.address;
339        assert_eq!(address, address!("0x533AE9D683B10C02EBDB05471642F85230071FC3"));
340    }
341
342    #[test]
343    fn create2_init_code() {
344        let init_code = "00";
345        let args =
346            Create2Args::parse_from(["foundry-cli", "--starts-with=cc", "--init-code", init_code]);
347        let create2_out = args.run().unwrap();
348        let address = create2_out.address;
349        assert!(format!("{address:x}").starts_with("cc"));
350        let salt = create2_out.salt;
351        let deployer = Address::from_str(DEPLOYER).unwrap();
352        assert_eq!(address, deployer.create2_from_code(salt, hex::decode(init_code).unwrap()));
353    }
354
355    #[test]
356    fn create2_init_code_hash() {
357        let init_code_hash = "bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a";
358        let args = Create2Args::parse_from([
359            "foundry-cli",
360            "--starts-with=dd",
361            "--init-code-hash",
362            init_code_hash,
363        ]);
364        let create2_out = args.run().unwrap();
365        let address = create2_out.address;
366        assert!(format!("{address:x}").starts_with("dd"));
367
368        let salt = create2_out.salt;
369        let deployer = Address::from_str(DEPLOYER).unwrap();
370
371        assert_eq!(
372            address,
373            deployer
374                .create2(salt, B256::from_slice(hex::decode(init_code_hash).unwrap().as_slice()))
375        );
376    }
377
378    #[test]
379    fn create2_caller() {
380        let init_code_hash = "bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a";
381        let args = Create2Args::parse_from([
382            "foundry-cli",
383            "--starts-with=dd",
384            "--init-code-hash",
385            init_code_hash,
386            "--caller=0x66f9664f97F2b50F62D13eA064982f936dE76657",
387        ]);
388        let create2_out = args.run().unwrap();
389        let address = create2_out.address;
390        let salt = create2_out.salt;
391        assert!(format!("{address:x}").starts_with("dd"));
392        assert!(format!("{salt:x}").starts_with("66f9664f97f2b50f62d13ea064982f936de76657"));
393    }
394
395    #[test]
396    fn deterministic_seed() {
397        let args = Create2Args::parse_from([
398            "foundry-cli",
399            "--starts-with=0x00",
400            "--init-code-hash=0x479d7e8f31234e208d704ba1a123c76385cea8a6981fd675b784fbd9cffb918d",
401            "--seed=0x479d7e8f31234e208d704ba1a123c76385cea8a6981fd675b784fbd9cffb918d",
402            "-j1",
403        ]);
404        let out = args.run().unwrap();
405        assert_eq!(out.address, address!("0x00614b3D65ac4a09A376a264fE1aE5E5E12A6C43"));
406        assert_eq!(
407            out.salt,
408            b256!("0x322113f523203e2c0eb00bbc8e69208b0eb0c8dad0eaac7b01d64ff016edb40d"),
409        );
410    }
411
412    #[test]
413    fn deterministic_output() {
414        let args = Create2Args::parse_from([
415            "foundry-cli",
416            "--starts-with=0x00",
417            "--init-code-hash=0x479d7e8f31234e208d704ba1a123c76385cea8a6981fd675b784fbd9cffb918d",
418            "--no-random",
419            "-j1",
420        ]);
421        let out = args.run().unwrap();
422        assert_eq!(out.address, address!("0x00bF495b8b42fdFeb91c8bCEB42CA4eE7186AEd2"));
423        assert_eq!(
424            out.salt,
425            b256!("0x000000000000000000000000000000000000000000000000df00000000000000"),
426        );
427    }
428
429    #[test]
430    fn j0() {
431        let args = Create2Args::try_parse_from([
432            "foundry-cli",
433            "--starts-with=00",
434            "--init-code-hash",
435            &B256::ZERO.to_string(),
436            "-j0",
437        ])
438        .unwrap();
439        assert_eq!(args.threads, Some(0));
440    }
441}