Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: better cast create2 #6212

Merged
merged 4 commits into from
Nov 3, 2023
Merged
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 98 additions & 92 deletions crates/cast/bin/cmd/create2.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
use alloy_primitives::{keccak256, Address, B256, U256};
use clap::Parser;
use eyre::{Result, WrapErr};
use rayon::prelude::*;
use regex::RegexSetBuilder;
use std::{str::FromStr, time::Instant};
use std::{
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
time::Instant,
};

/// CLI arguments for `cast create2`.
#[derive(Debug, Clone, Parser)]
Expand Down Expand Up @@ -45,12 +50,16 @@ pub struct Create2Args {
/// Init code hash of the contract to be deployed.
#[clap(alias = "ch", long, value_name = "HASH")]
init_code_hash: Option<String>,

/// Number of threads to use. Defaults to and caps at the number of logical cores.
#[clap(short, long)]
jobs: Option<usize>,
}

#[allow(dead_code)]
pub struct Create2Output {
pub address: Address,
pub salt: U256,
pub salt: B256,
}

impl Create2Args {
Expand All @@ -63,6 +72,7 @@ impl Create2Args {
deployer,
init_code,
init_code_hash,
jobs,
} = self;

let mut regexs = vec![];
Expand Down Expand Up @@ -114,34 +124,76 @@ impl Create2Args {
keccak256(hex::decode(init_code)?)
};

let mut n_threads = std::thread::available_parallelism().map_or(1, |n| n.get());
if let Some(jobs) = jobs {
n_threads = n_threads.min(jobs);
}
if cfg!(test) {
n_threads = n_threads.min(2);
}

println!("Starting to generate deterministic contract address...");
let mut handles = Vec::with_capacity(n_threads);
let found = Arc::new(AtomicBool::new(false));
let timer = Instant::now();
let (salt, addr) = std::iter::repeat(())
.par_bridge()
.map(|_| {
let salt = B256::random();

let addr = deployer.create2(salt, init_code_hash).to_checksum(None);

(salt, addr)
})
.find_any(move |(_, addr)| {
let addr = addr.to_string();
let addr = addr.strip_prefix("0x").unwrap();
regex.matches(addr).into_iter().count() == regex.patterns().len()
})
.unwrap();

let salt = U256::from_be_bytes(*salt);
let address = Address::from_str(&addr).unwrap();

println!(
"Successfully found contract address in {} seconds.\nAddress: {}\nSalt: {}",
timer.elapsed().as_secs(),
addr,
salt
);

// Loops through all possible salts in parallel until a result is found.
// Each thread iterates over `(i..).step_by(n_threads)`.
for i in 0..n_threads {
// Create local copies for the thread.
let increment = n_threads;
let regex = regex.clone();
let regex_len = regex.patterns().len();
let found = Arc::clone(&found);
handles.push(std::thread::spawn(move || {
// Read the first bytes of the salt as a usize to be able to increment it.
struct B256Aligned(B256, [usize; 0]);
let mut salt = B256Aligned(B256::ZERO, []);
// SAFETY: B256 is aligned to `usize`.
let salt_word = unsafe { &mut *salt.0.as_mut_ptr().cast::<usize>() };

// Important: set the salt to the start value, otherwise all threads loop over the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the loop only covers u64 or u32 depending on the arch, so not all the available 32bytes?

fine?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

>> u32::MAX / 128
33554431
>> u64::MAX / 128
144115188075855871

iterations needed to overflow, I don't think that's reachable

// same values.
*salt_word = i;

let mut checksum = [0; 42];
loop {
// Stop if a result was found in another thread.
if found.load(Ordering::Relaxed) {
break None;
}

// Calculate the `CREATE2` address.
#[allow(clippy::needless_borrows_for_generic_args)]
let addr = deployer.create2(&salt.0, init_code_hash);

// Check if the the regex matches the calculated address' checksum.
let _ = addr.to_checksum_raw(&mut checksum, None);
// SAFETY: stripping 2 ASCII bytes ("0x") off of an already valid UTF-8 string
// is safe.
let s = unsafe { std::str::from_utf8_unchecked(checksum.get_unchecked(2..)) };
if regex.matches(s).into_iter().count() == regex_len {
// Notify other threads that we found a result.
found.store(true, Ordering::Relaxed);
break Some((salt.0, addr));
}

// Increment the salt for the next iteration.
*salt_word += increment;
}
}));
}

let results = handles.into_iter().filter_map(|h| h.join().unwrap()).collect::<Vec<_>>();
println!("Successfully found contract address(es) in {:?}", timer.elapsed());
for (i, (salt, address)) in results.iter().enumerate() {
if i > 0 {
println!("---");
}
println!("Address: {address}\nSalt: {salt} ({})", U256::from_be_bytes(salt.0));
}

let (salt, address) = results.into_iter().next().unwrap();
Ok(Create2Output { address, salt })
}
}
Expand All @@ -156,6 +208,7 @@ fn get_regex_hex_string(s: String) -> Result<String> {
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;

const DEPLOYER: &str = "0x4e59b44847b379578588920ca78fbf26c0b4956c";

Expand All @@ -164,42 +217,30 @@ mod tests {
// even hex chars
let args = Create2Args::parse_from(["foundry-cli", "--starts-with", "aa"]);
let create2_out = args.run().unwrap();
let address = create2_out.address;
let address = format!("{address:x}");
assert!(format!("{:x}", create2_out.address).starts_with("aa"));

assert!(address.starts_with("aa"));
let args = Create2Args::parse_from(["foundry-cli", "--ends-with", "bb"]);
let create2_out = args.run().unwrap();
assert!(format!("{:x}", create2_out.address).ends_with("bb"));

// odd hex chars
let args = Create2Args::parse_from(["foundry-cli", "--starts-with", "aaa"]);
let create2_out = args.run().unwrap();
let address = create2_out.address;
let address = format!("{address:x}");
assert!(format!("{:x}", create2_out.address).starts_with("aaa"));

assert!(address.starts_with("aaa"));
let args = Create2Args::parse_from(["foundry-cli", "--ends-with", "bbb"]);
let create2_out = args.run().unwrap();
assert!(format!("{:x}", create2_out.address).ends_with("bbb"));

// even hex chars with 0x prefix
let args = Create2Args::parse_from(["foundry-cli", "--starts-with", "0xaa"]);
let create2_out = args.run().unwrap();
let address = create2_out.address;
let address = format!("{address:x}");

assert!(address.starts_with("aa"));
assert!(format!("{:x}", create2_out.address).starts_with("aa"));

// odd hex chars with 0x prefix
let args = Create2Args::parse_from(["foundry-cli", "--starts-with", "0xaaa"]);
let create2_out = args.run().unwrap();
let address = create2_out.address;
let address = format!("{address:x}");

assert!(address.starts_with("aaa"));

// odd hex chars with 0x suffix
let args = Create2Args::parse_from(["foundry-cli", "--ends-with", "bb"]);
let create2_out = args.run().unwrap();
let address = create2_out.address;
let address = format!("{address:x}");

assert!(address.ends_with("bb"));
assert!(format!("{:x}", create2_out.address).starts_with("aaa"));

// check fails on wrong chars
let args = Create2Args::parse_from(["foundry-cli", "--starts-with", "0xerr"]);
Expand All @@ -221,9 +262,7 @@ mod tests {
]);
let create2_out = args.run().unwrap();
let address = create2_out.address;
let address = format!("{address:x}");

assert!(address.starts_with("bb"));
assert!(format!("{address:x}").starts_with("bb"));
}

#[test]
Expand All @@ -238,12 +277,10 @@ mod tests {
]);
let create2_out = args.run().unwrap();
let address = create2_out.address;
let address_str = format!("{address:x}");
assert!(format!("{address:x}").starts_with("cc"));
let salt = create2_out.salt;
let deployer = Address::from_str(DEPLOYER).unwrap();

assert!(address_str.starts_with("cc"));
assert_eq!(address, verify_create2(deployer, salt, hex::decode(init_code).unwrap()));
assert_eq!(address, deployer.create2_from_code(salt, hex::decode(init_code).unwrap()));
}

#[test]
Expand All @@ -258,46 +295,15 @@ mod tests {
]);
let create2_out = args.run().unwrap();
let address = create2_out.address;
let address_str = format!("{address:x}");
assert!(format!("{address:x}").starts_with("dd"));

let salt = create2_out.salt;
let deployer = Address::from_str(DEPLOYER).unwrap();

assert!(address_str.starts_with("dd"));
assert_eq!(
address,
verify_create2_hash(deployer, salt, hex::decode(init_code_hash).unwrap())
deployer
.create2(salt, B256::from_slice(hex::decode(init_code_hash).unwrap().as_slice()))
);
}

#[test]
fn verify_helpers() {
// https://eips.ethereum.org/EIPS/eip-1014
let eip_address = Address::from_str("0x4D1A2e2bB4F88F0250f26Ffff098B0b30B26BF38").unwrap();

let deployer = Address::from_str("0x0000000000000000000000000000000000000000").unwrap();
let salt =
U256::from_str("0x0000000000000000000000000000000000000000000000000000000000000000")
.unwrap();
let init_code = hex::decode("00").unwrap();
let address = verify_create2(deployer, salt, init_code);

assert_eq!(address, eip_address);

let init_code_hash =
hex::decode("bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a")
.unwrap();
let address = verify_create2_hash(deployer, salt, init_code_hash);

assert_eq!(address, eip_address);
}

fn verify_create2(deployer: Address, salt: U256, init_code: Vec<u8>) -> Address {
let init_code_hash = keccak256(init_code);
deployer.create2(salt.to_be_bytes(), init_code_hash)
}

fn verify_create2_hash(deployer: Address, salt: U256, init_code_hash: Vec<u8>) -> Address {
let init_code_hash = B256::from_slice(&init_code_hash);
deployer.create2(salt.to_be_bytes(), init_code_hash)
}
}
Loading