From 762d20abdbe973bfb409472e35d0e1e8a106f5a3 Mon Sep 17 00:00:00 2001 From: Kevaundray Wedderburn Date: Sun, 11 May 2025 22:55:05 +0100 Subject: [PATCH] add ere-sp1 --- crates/ere-sp1/Cargo.toml | 23 ++++ crates/ere-sp1/src/compile.rs | 196 ++++++++++++++++++++++++++++++++++ crates/ere-sp1/src/lib.rs | 25 +++++ 3 files changed, 244 insertions(+) create mode 100644 crates/ere-sp1/Cargo.toml create mode 100644 crates/ere-sp1/src/compile.rs create mode 100644 crates/ere-sp1/src/lib.rs diff --git a/crates/ere-sp1/Cargo.toml b/crates/ere-sp1/Cargo.toml new file mode 100644 index 0000000..607ddd8 --- /dev/null +++ b/crates/ere-sp1/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "ere-sp1" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true + +[dependencies] +sp1-sdk = "4.2.0" +dotenv = "0.15.0" +zkvm-interface = { workspace = true } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +toml = "0.8" +tempfile = "3.3" +clap = { version = "4.4", features = ["derive"] } +bincode = "1.3" +thiserror = "2" +tracing = "0.1" + +[lib] +name = "ere_succinct" +path = "src/lib.rs" diff --git a/crates/ere-sp1/src/compile.rs b/crates/ere-sp1/src/compile.rs new file mode 100644 index 0000000..85b7e1d --- /dev/null +++ b/crates/ere-sp1/src/compile.rs @@ -0,0 +1,196 @@ +use std::{ + fs, + path::{Path, PathBuf}, + process::{Command, ExitStatus}, +}; + +use tempfile::TempDir; +use thiserror::Error; +use toml::Value as TomlValue; +use tracing::info; + +/// Errors that can be encountered while compiling a SP1 program +#[derive(Debug, Error)] +pub enum CompileError { + #[error("Program path does not exist or is not a directory: {0}")] + InvalidProgramPath(PathBuf), + #[error( + "Cargo.toml not found in program directory: {program_dir}. Expected at: {manifest_path}" + )] + CargoTomlMissing { + program_dir: PathBuf, + manifest_path: PathBuf, + }, + #[error("Could not find `[package].name` in guest Cargo.toml at {path}")] + MissingPackageName { path: PathBuf }, + #[error("Compiled ELF not found at expected path: {0}")] + ElfNotFound(PathBuf), + #[error("`cargo prove build` failed with status: {status} for program at {path}")] + CargoBuildFailed { status: ExitStatus, path: PathBuf }, + #[error("Failed to read file at {path}: {source}")] + ReadFile { + path: PathBuf, + #[source] + source: std::io::Error, + }, + #[error("Failed to parse guest Cargo.toml at {path}: {source}")] + ParseCargoToml { + path: PathBuf, + #[source] + source: toml::de::Error, + }, + #[error("Failed to execute `cargo prove build` in {cwd}: {source}")] + CargoProveBuild { + cwd: PathBuf, + #[source] + source: std::io::Error, + }, + #[error("Failed to create temporary output directory: {0}")] + TempDir(#[from] std::io::Error), +} + +/// Compile the guest crate and return raw ELF bytes. +pub fn compile_sp1_program(program_crate_path: &Path) -> Result, CompileError> { + info!("Compiling SP1 program at {}", program_crate_path.display()); + + if !program_crate_path.exists() || !program_crate_path.is_dir() { + return Err(CompileError::InvalidProgramPath( + program_crate_path.to_path_buf(), + )); + } + + let guest_manifest_path = program_crate_path.join("Cargo.toml"); + if !guest_manifest_path.exists() { + return Err(CompileError::CargoTomlMissing { + program_dir: program_crate_path.to_path_buf(), + manifest_path: guest_manifest_path.clone(), + }); + } + + // ── read + parse Cargo.toml ─────────────────────────────────────────── + let manifest_content = + fs::read_to_string(&guest_manifest_path).map_err(|e| CompileError::ReadFile { + path: guest_manifest_path.clone(), + source: e, + })?; + + let manifest_toml: TomlValue = + manifest_content + .parse::() + .map_err(|e| CompileError::ParseCargoToml { + path: guest_manifest_path.clone(), + source: e, + })?; + + let program_name = manifest_toml + .get("package") + .and_then(|p| p.get("name")) + .and_then(|n| n.as_str()) + .ok_or_else(|| CompileError::MissingPackageName { + path: guest_manifest_path.clone(), + })?; + + info!("Parsed program name: {program_name}"); + + // ── build into a temp dir ───────────────────────────────────────────── + let temp_output_dir = TempDir::new_in(program_crate_path)?; + let temp_output_dir_path = temp_output_dir.path(); + let elf_name = format!("{program_name}.elf"); + + info!( + "Running `cargo prove build` → dir: {}, ELF: {}", + temp_output_dir_path.display(), + elf_name + ); + + let status = Command::new("cargo") + .current_dir(program_crate_path) + .args([ + "prove", + "build", + "--output-directory", + temp_output_dir_path.to_str().unwrap(), + "--elf-name", + &elf_name, + ]) + .stdout(std::process::Stdio::inherit()) + .stderr(std::process::Stdio::inherit()) + .status() + .map_err(|e| CompileError::CargoProveBuild { + cwd: program_crate_path.to_path_buf(), + source: e, + })?; + + if !status.success() { + return Err(CompileError::CargoBuildFailed { + status, + path: program_crate_path.to_path_buf(), + }); + } + + let elf_path = temp_output_dir_path.join(&elf_name); + if !elf_path.exists() { + return Err(CompileError::ElfNotFound(elf_path)); + } + + let elf_bytes = fs::read(&elf_path).map_err(|e| CompileError::ReadFile { + path: elf_path, + source: e, + })?; + + info!("SP1 program compiled OK – {} bytes", elf_bytes.len()); + Ok(elf_bytes) +} + +#[cfg(test)] +mod tests { + use zkvm_interface::Compiler; + + use crate::EreSP1; + + use super::*; + use std::path::PathBuf; + + // TODO: for now, we just get one test file + // TODO: but this should get the whole directory and compile each test + fn get_compile_test_guest_program_path() -> PathBuf { + let workspace_dir = env!("CARGO_WORKSPACE_DIR"); + PathBuf::from(workspace_dir) + .join("tests") + .join("sp1") + .join("compile") + .join("basic") + .canonicalize() + .expect("Failed to find or canonicalize test guest program at /tests/compile/sp1") + } + + #[test] + fn test_compile_sp1_program() { + let test_guest_path = get_compile_test_guest_program_path(); + + match compile_sp1_program(&test_guest_path) { + Ok(elf_bytes) => { + assert!(!elf_bytes.is_empty(), "ELF bytes should not be empty."); + } + Err(e) => { + panic!("compile failed for dedicated guest: {:?}", e); + } + } + } + + #[test] + fn test_compile_trait() { + let test_guest_path = get_compile_test_guest_program_path(); + match EreSP1::compile(&test_guest_path) { + Ok(elf_bytes) => { + assert!(!elf_bytes.is_empty(), "ELF bytes should not be empty."); + } + Err(e) => { + panic!( + "compile_sp1_program direct call failed for dedicated guest: {:?}", + e + ); + } + } + } +} diff --git a/crates/ere-sp1/src/lib.rs b/crates/ere-sp1/src/lib.rs new file mode 100644 index 0000000..ae9135c --- /dev/null +++ b/crates/ere-sp1/src/lib.rs @@ -0,0 +1,25 @@ +#![cfg_attr(not(test), warn(unused_crate_dependencies))] + +use compile::compile_sp1_program; +use zkvm_interface::Compiler; + +mod compile; + +// Represents Ere compliant API for SP1 +pub struct EreSP1; + +#[derive(Debug, thiserror::Error)] +pub enum SP1Error { + #[error(transparent)] + CompileError(#[from] compile::CompileError), +} + +impl Compiler for EreSP1 { + type Error = SP1Error; + + type Program = Vec; + + fn compile(path_to_program: &std::path::Path) -> Result { + compile_sp1_program(path_to_program).map_err(SP1Error::from) + } +}