use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::process::Command;
use std::{env, fs};
#[allow(clippy::too_many_lines, clippy::format_push_string)]
fn main() {
if env::var("CARGO_FEATURE_BUILD_SCRIPT").is_err() {
return;
}
let contract_dirs = vec![
"./dependencies/eigenlayer-middleware-1.3.1/lib/eigenlayer-contracts",
"./dependencies/eigenlayer-middleware-1.3.1",
"./contracts",
];
soldeer_install();
soldeer_update();
build_contracts(contract_dirs);
let src_dir = Path::new("src");
let bindings_dir = src_dir.join("bindings");
if bindings_dir.exists() {
fs::remove_dir_all(&bindings_dir).unwrap();
}
fs::create_dir_all(&bindings_dir).unwrap();
let contracts_contracts = ["SquaringTask", "SquaringServiceManager"];
let middleware_contracts = [
"IAllocationManager",
"AllocationManager",
"AVSDirectory",
"BLSApkRegistry",
"DelegationManager",
"EigenPod",
"EigenPodManager",
"EmptyContract",
"ISlashingRegistryCoordinator",
"IndexRegistry",
"InstantSlasher",
"OperatorStateRetriever",
"PauserRegistry",
"ProxyAdmin",
"PermissionController",
"RegistryCoordinator",
"RewardsCoordinator",
"IServiceManager",
"SlashingRegistryCoordinator",
"SocketRegistry",
"StakeRegistry",
"StrategyBase",
"StrategyFactory",
"StrategyManager",
"TransparentUpgradeableProxy",
"UpgradeableBeacon",
"IStrategy",
"QuorumBitmapHistoryLib",
"SignatureCheckerLib",
];
println!("Generating bindings for contracts...");
let mut cmd = Command::new("forge");
cmd.args([
"bind",
"--alloy",
"--alloy-version",
"1.0",
"--skip-build",
"--evm-version",
"shanghai",
"--bindings-path",
"src/bindings/deploy",
"--overwrite",
"--root",
"./contracts",
"--module",
]);
for contract in &contracts_contracts {
cmd.args(["--select", &format!("^{}$", contract)]);
}
let status = cmd
.status()
.expect("Failed to execute forge bind command for contracts");
assert!(status.success());
println!("Generating bindings for middleware...");
let mut cmd = Command::new("forge");
cmd.args([
"bind",
"--alloy",
"--alloy-version",
"1.0",
"--skip-build",
"--evm-version",
"shanghai",
"--bindings-path",
"src/bindings/core",
"--overwrite",
"--root",
"./dependencies/eigenlayer-middleware-1.3.1",
"--module",
]);
for contract in &middleware_contracts {
cmd.args(["--select", &format!("^{}$", contract)]);
}
let status = cmd
.status()
.expect("Failed to execute forge bind command for middleware");
assert!(status.success());
println!("Post-processing generated files...");
for contract in &contracts_contracts {
let lower_contract = to_snake_case(contract);
let file_path = format!("src/bindings/deploy/{}.rs", lower_contract);
add_imports_to_file(&file_path, contract);
}
for contract in &middleware_contracts {
let lower_contract = to_snake_case(contract);
let file_path = format!("src/bindings/core/{}.rs", lower_contract);
add_imports_to_file(&file_path, contract);
if *contract == "AllocationManager" || *contract == "IAllocationManager" {
let path = Path::new(&file_path);
let mut file =
fs::File::open(path).unwrap_or_else(|_| panic!("Failed to modify {}", file_path));
let mut contents = String::new();
file.read_to_string(&mut contents)
.unwrap_or_else(|_| panic!("Failed to read {}", file_path));
let new_contents = contents.replace(
"#[derive(Clone)]\n pub struct AllocateParams {",
"#[derive(Clone, Hash, Debug, Eq, PartialEq)]\n pub struct AllocateParams {",
);
fs::write(path, new_contents)
.unwrap_or_else(|_| panic!("Failed to write to {}", file_path));
}
}
let mut contents = String::new();
contents.push_str("pub mod core;\n");
contents.push_str("pub mod deploy;\n");
contents.push('\n');
for contract in &contracts_contracts {
let lower_contract = to_snake_case(contract);
contents.push_str(&format!(
"pub use deploy::{}::{};\n",
lower_contract, contract
));
}
for contract in &middleware_contracts {
let lower_contract = to_snake_case(contract);
contents.push_str(&format!(
"pub use core::{}::{};\n",
lower_contract, contract
));
}
let path = Path::new("src/bindings/mod.rs");
fs::write(path, contents).expect("Failed to write to mod.rs");
let mut core_mod_contents = String::new();
core_mod_contents.push_str("// This file is generated by the build script\n");
core_mod_contents.push_str("// Do not edit manually\n\n");
for contract in &middleware_contracts {
let lower_contract = to_snake_case(contract);
core_mod_contents.push_str(&format!("pub mod {};\n", lower_contract));
}
core_mod_contents.push_str("\n// Re-export OperatorSet for use across modules\n");
core_mod_contents
.push_str("pub use self::allocation_manager::AllocationManager::OperatorSet;\n");
let core_mod_path = Path::new("src/bindings/core/mod.rs");
fs::write(core_mod_path, core_mod_contents).expect("Failed to write to core/mod.rs");
let mut deploy_mod_contents = String::new();
deploy_mod_contents.push_str("// This file is generated by the build script\n");
deploy_mod_contents.push_str("// Do not edit manually\n\n");
for contract in &contracts_contracts {
let lower_contract = to_snake_case(contract);
deploy_mod_contents.push_str(&format!("pub mod {};\n", lower_contract));
}
deploy_mod_contents.push_str("\n// Import OperatorSet from core\n");
deploy_mod_contents.push_str("pub use crate::bindings::core::OperatorSet;\n");
let deploy_mod_path = Path::new("src/bindings/deploy/mod.rs");
fs::write(deploy_mod_path, deploy_mod_contents).expect("Failed to write to deploy/mod.rs");
}
fn add_imports_to_file(file_path: &str, contract: &str) {
let path = Path::new(file_path);
if !path.exists() {
println!("Warning: File {} does not exist", file_path);
return;
}
let mut file = fs::File::open(path).unwrap_or_else(|_| panic!("Failed to open {}", file_path));
let mut contents = String::new();
file.read_to_string(&mut contents)
.unwrap_or_else(|_| panic!("Failed to read {}", file_path));
let new_contents = format!(
"#![allow(clippy::all, clippy::pedantic, clippy::nursery, warnings, unknown_lints, rustdoc::all, elided_lifetimes_in_paths)]\nuse {}::*;\n\n{}",
contract, contents
);
let mut file =
fs::File::create(path).unwrap_or_else(|_| panic!("Failed to create {}", file_path));
file.write_all(new_contents.as_bytes())
.unwrap_or_else(|_| panic!("Failed to write to {}", file_path));
}
pub fn build_contracts(contract_dirs: Vec<&str>) {
let root = workspace_or_manifest_dir();
let forge_executable = find_forge_executable();
for dir in contract_dirs {
let full_path = root.join(dir).canonicalize().unwrap_or_else(|_| {
println!(
"Directory not found or inaccessible: {}",
root.join(dir).display()
);
root.join(dir)
});
if full_path.exists() {
if full_path != root.join("./contracts") {
let foundry_toml_path = full_path.join("foundry.toml");
if foundry_toml_path.exists() {
let mut content = String::new();
std::fs::File::open(&foundry_toml_path)
.expect("Failed to open foundry.toml")
.read_to_string(&mut content)
.expect("Failed to read foundry.toml");
if !content.contains("evm_version") {
if let Some(pos) = content.find("[profile.default]") {
let mut new_content = content.clone();
let insert_pos = content[pos..]
.find('\n')
.map_or(content.len(), |p| p + pos + 1);
new_content.insert_str(insert_pos, " evm_version = \"shanghai\"\n");
std::fs::write(&foundry_toml_path, new_content)
.expect("Failed to write to foundry.toml");
} else {
let mut file = std::fs::OpenOptions::new()
.append(true)
.open(&foundry_toml_path)
.expect("Failed to open foundry.toml for appending");
file.write_all(b"\n[profile.default]\nevm_version = \"shanghai\"\n")
.expect("Failed to append to foundry.toml");
}
}
} else {
panic!("Failed to read dependency foundry.toml");
}
}
let status = Command::new(&forge_executable)
.current_dir(&full_path)
.arg("build")
.arg("--evm-version")
.arg("shanghai")
.arg("--use")
.arg("0.8.27")
.status()
.expect("Failed to execute Forge build");
assert!(
status.success(),
"Forge build failed for directory: {}",
full_path.display()
);
} else {
panic!(
"Directory not found or does not exist: {}",
full_path.display()
);
}
}
}
fn is_directory_empty(path: &Path) -> bool {
fs::read_dir(path)
.map(|mut i| i.next().is_none())
.unwrap_or(true)
}
fn workspace_or_manifest_dir() -> PathBuf {
let dir = env::var("CARGO_WORKSPACE_DIR")
.or_else(|_| env::var("CARGO_MANIFEST_DIR"))
.expect("neither CARGO_WORKSPACE_DIR nor CARGO_MANIFEST_DIR is set");
PathBuf::from(dir)
}
pub fn soldeer_install() {
let root = workspace_or_manifest_dir();
let dependencies_dir = root.join("dependencies");
if !dependencies_dir.exists() || is_directory_empty(&dependencies_dir) {
let forge_executable = find_forge_executable();
println!("Populating dependencies directory");
let status = Command::new(&forge_executable)
.current_dir(&root)
.args(["soldeer", "install"])
.status()
.expect("Failed to execute 'forge soldeer install'");
assert!(status.success(), "'forge soldeer install' failed");
} else {
println!("Dependencies directory exists or is not empty. Skipping soldeer install.");
}
}
pub fn soldeer_update() {
let root = workspace_or_manifest_dir();
let forge_executable = find_forge_executable();
let status = Command::new(&forge_executable)
.current_dir(&root)
.args(["soldeer", "update", "-d"])
.status()
.expect("Failed to execute 'forge soldeer update'");
assert!(status.success(), "'forge soldeer update' failed");
}
#[must_use]
pub fn find_forge_executable() -> String {
match Command::new("which").arg("forge").output() {
Ok(output) => {
let path = String::from_utf8_lossy(&output.stdout).trim().to_string();
assert!(
!path.is_empty(),
"Forge executable not found. Make sure Foundry is installed."
);
path
}
Err(e) => panic!("Failed to find `forge` executable: {e}"),
}
}
fn to_snake_case(input: &str) -> String {
let mut result = String::new();
let chars: Vec<char> = input.chars().collect();
for (i, &ch) in chars.iter().enumerate() {
let is_first = i == 0;
let is_last = i == chars.len() - 1;
let prev_char = if i > 0 { Some(chars[i - 1]) } else { None };
let next_char = if i < chars.len() - 1 {
Some(chars[i + 1])
} else {
None
};
if ch.is_uppercase() {
let should_add_underscore = !is_first
&& (
prev_char.is_some_and(char::is_lowercase) ||
(!is_last && next_char.is_some_and(char::is_lowercase))
);
if should_add_underscore {
result.push('_');
}
result.push(ch.to_lowercase().next().unwrap());
} else {
result.push(ch.to_lowercase().next().unwrap());
}
}
result
}