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}")?;
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_println!("Configuration:")?;
187        sh_println!("Init code hash: {init_code_hash}")?;
188        sh_println!("Regex patterns: {:?}\n", regex.patterns())?;
189        sh_println!(
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_println!("Successfully found contract address in {:?}", timer.elapsed())?;
214        sh_println!("Address: {address}")?;
215        sh_println!("Salt: {salt} ({})", U256::from_be_bytes(salt.0))?;
216
217        Ok(Create2Output { address, salt })
218    }
219}
220
221fn get_regex_hex_string(s: String) -> Result<String> {
222    let s = s.strip_prefix("0x").unwrap_or(&s);
223    let pad_width = s.len() + s.len() % 2;
224    hex::decode(format!("{s:0<pad_width$}"))?;
225    Ok(s.to_string())
226}
227
228#[cfg(test)]
229mod tests {
230    use super::*;
231    use alloy_primitives::{address, b256};
232    use std::str::FromStr;
233
234    #[test]
235    fn basic_create2() {
236        let mk_args = |args: &[&str]| {
237            Create2Args::parse_from(["foundry-cli", "--init-code-hash=0x0000000000000000000000000000000000000000000000000000000000000000"].iter().chain(args))
238        };
239
240        // even hex chars
241        let args = mk_args(&["--starts-with", "aa"]);
242        let create2_out = args.run().unwrap();
243        assert!(format!("{:x}", create2_out.address).starts_with("aa"));
244
245        let args = mk_args(&["--ends-with", "bb"]);
246        let create2_out = args.run().unwrap();
247        assert!(format!("{:x}", create2_out.address).ends_with("bb"));
248
249        // odd hex chars
250        let args = mk_args(&["--starts-with", "aaa"]);
251        let create2_out = args.run().unwrap();
252        assert!(format!("{:x}", create2_out.address).starts_with("aaa"));
253
254        let args = mk_args(&["--ends-with", "bbb"]);
255        let create2_out = args.run().unwrap();
256        assert!(format!("{:x}", create2_out.address).ends_with("bbb"));
257
258        // even hex chars with 0x prefix
259        let args = mk_args(&["--starts-with", "0xaa"]);
260        let create2_out = args.run().unwrap();
261        assert!(format!("{:x}", create2_out.address).starts_with("aa"));
262
263        // odd hex chars with 0x prefix
264        let args = mk_args(&["--starts-with", "0xaaa"]);
265        let create2_out = args.run().unwrap();
266        assert!(format!("{:x}", create2_out.address).starts_with("aaa"));
267
268        // check fails on wrong chars
269        let args = mk_args(&["--starts-with", "0xerr"]);
270        let create2_out = args.run();
271        assert!(create2_out.is_err());
272
273        // check fails on wrong x prefixed string provided
274        let args = mk_args(&["--starts-with", "x00"]);
275        let create2_out = args.run();
276        assert!(create2_out.is_err());
277    }
278
279    #[test]
280    fn matches_pattern() {
281        let args = Create2Args::parse_from([
282            "foundry-cli",
283            "--init-code-hash=0x0000000000000000000000000000000000000000000000000000000000000000",
284            "--matching=0xbbXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
285        ]);
286        let create2_out = args.run().unwrap();
287        let address = create2_out.address;
288        assert!(format!("{address:x}").starts_with("bb"));
289    }
290
291    #[test]
292    fn create2_salt() {
293        let args = Create2Args::parse_from([
294            "foundry-cli",
295            "--deployer=0x8ba1f109551bD432803012645Ac136ddd64DBA72",
296            "--salt=0x7c5ea36004851c764c44143b1dcb59679b11c9a68e5f41497f6cf3d480715331",
297            "--init-code=0x6394198df16000526103ff60206004601c335afa6040516060f3",
298        ]);
299        let create2_out = args.run().unwrap();
300        let address = create2_out.address;
301        assert_eq!(address, address!("0x533AE9D683B10C02EBDB05471642F85230071FC3"));
302    }
303
304    #[test]
305    fn create2_init_code() {
306        let init_code = "00";
307        let args =
308            Create2Args::parse_from(["foundry-cli", "--starts-with=cc", "--init-code", init_code]);
309        let create2_out = args.run().unwrap();
310        let address = create2_out.address;
311        assert!(format!("{address:x}").starts_with("cc"));
312        let salt = create2_out.salt;
313        let deployer = Address::from_str(DEPLOYER).unwrap();
314        assert_eq!(address, deployer.create2_from_code(salt, hex::decode(init_code).unwrap()));
315    }
316
317    #[test]
318    fn create2_init_code_hash() {
319        let init_code_hash = "bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a";
320        let args = Create2Args::parse_from([
321            "foundry-cli",
322            "--starts-with=dd",
323            "--init-code-hash",
324            init_code_hash,
325        ]);
326        let create2_out = args.run().unwrap();
327        let address = create2_out.address;
328        assert!(format!("{address:x}").starts_with("dd"));
329
330        let salt = create2_out.salt;
331        let deployer = Address::from_str(DEPLOYER).unwrap();
332
333        assert_eq!(
334            address,
335            deployer
336                .create2(salt, B256::from_slice(hex::decode(init_code_hash).unwrap().as_slice()))
337        );
338    }
339
340    #[test]
341    fn create2_caller() {
342        let init_code_hash = "bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a";
343        let args = Create2Args::parse_from([
344            "foundry-cli",
345            "--starts-with=dd",
346            "--init-code-hash",
347            init_code_hash,
348            "--caller=0x66f9664f97F2b50F62D13eA064982f936dE76657",
349        ]);
350        let create2_out = args.run().unwrap();
351        let address = create2_out.address;
352        let salt = create2_out.salt;
353        assert!(format!("{address:x}").starts_with("dd"));
354        assert!(format!("{salt:x}").starts_with("66f9664f97f2b50f62d13ea064982f936de76657"));
355    }
356
357    #[test]
358    fn deterministic_seed() {
359        let args = Create2Args::parse_from([
360            "foundry-cli",
361            "--starts-with=0x00",
362            "--init-code-hash=0x479d7e8f31234e208d704ba1a123c76385cea8a6981fd675b784fbd9cffb918d",
363            "--seed=0x479d7e8f31234e208d704ba1a123c76385cea8a6981fd675b784fbd9cffb918d",
364            "-j1",
365        ]);
366        let out = args.run().unwrap();
367        assert_eq!(out.address, address!("0x00614b3D65ac4a09A376a264fE1aE5E5E12A6C43"));
368        assert_eq!(
369            out.salt,
370            b256!("0x322113f523203e2c0eb00bbc8e69208b0eb0c8dad0eaac7b01d64ff016edb40d"),
371        );
372    }
373
374    #[test]
375    fn deterministic_output() {
376        let args = Create2Args::parse_from([
377            "foundry-cli",
378            "--starts-with=0x00",
379            "--init-code-hash=0x479d7e8f31234e208d704ba1a123c76385cea8a6981fd675b784fbd9cffb918d",
380            "--no-random",
381            "-j1",
382        ]);
383        let out = args.run().unwrap();
384        assert_eq!(out.address, address!("0x00bF495b8b42fdFeb91c8bCEB42CA4eE7186AEd2"));
385        assert_eq!(
386            out.salt,
387            b256!("0x000000000000000000000000000000000000000000000000df00000000000000"),
388        );
389    }
390
391    #[test]
392    fn j0() {
393        let args = Create2Args::try_parse_from([
394            "foundry-cli",
395            "--starts-with=00",
396            "--init-code-hash",
397            &B256::ZERO.to_string(),
398            "-j0",
399        ])
400        .unwrap();
401        assert_eq!(args.threads, Some(0));
402    }
403}