mirror of
https://github.com/eth-act/ere.git
synced 2026-02-19 11:54:42 -05:00
add ere-sp1
This commit is contained in:
23
crates/ere-sp1/Cargo.toml
Normal file
23
crates/ere-sp1/Cargo.toml
Normal file
@@ -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"
|
||||
196
crates/ere-sp1/src/compile.rs
Normal file
196
crates/ere-sp1/src/compile.rs
Normal file
@@ -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<Vec<u8>, 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::<TomlValue>()
|
||||
.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 <CARGO_WORKSPACE_DIR>/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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
25
crates/ere-sp1/src/lib.rs
Normal file
25
crates/ere-sp1/src/lib.rs
Normal file
@@ -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<u8>;
|
||||
|
||||
fn compile(path_to_program: &std::path::Path) -> Result<Self::Program, Self::Error> {
|
||||
compile_sp1_program(path_to_program).map_err(SP1Error::from)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user