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