use crate::{
manifest,
plan::{Graph, NodeIx, Pinned, PinnedManifests, Plan},
};
use essential_types::{
contract::Contract, predicate::Predicate as CompiledPredicate, ContentAddress,
};
use pint_abi_types::ContractABI;
use pintc::asm_gen::compile_contract;
use std::{
collections::HashMap,
path::{Path, PathBuf},
};
use thiserror::Error;
pub struct PlanBuilder<'p> {
pub plan: &'p Plan,
built_pkgs: BuiltPkgs,
order: std::slice::Iter<'p, NodeIx>,
}
pub struct PrebuiltPkg<'p, 'b> {
pub plan: &'p Plan,
built_pkgs: &'b mut BuiltPkgs,
n: NodeIx,
}
pub type BuiltPkgs = HashMap<NodeIx, BuiltPkg>;
#[derive(Debug)]
pub enum BuiltPkg {
Contract(BuiltContract),
Library(BuiltLibrary),
}
#[derive(Debug)]
pub struct BuiltContract {
pub warnings: pintc::warning::Warnings,
pub predicate_metadata: Vec<PredicateMetadata>,
pub contract: Contract,
pub ca: ContentAddress,
pub lib_entry_point: PathBuf,
pub abi: ContractABI,
pub optimized: pintc::predicate::Contract,
}
#[derive(Debug)]
pub struct BuiltPredicate {
pub ca: ContentAddress,
pub name: String,
pub predicate: CompiledPredicate,
}
#[derive(Debug)]
pub struct PredicateMetadata {
pub ca: ContentAddress,
pub name: String,
}
#[derive(Debug)]
pub struct BuiltLibrary {
pub warnings: pintc::warning::Warnings,
pub contract: pintc::predicate::Contract,
}
#[derive(Debug)]
pub struct BuildError {
pub built_pkgs: BuiltPkgs,
pub pkg_err: BuildPkgError,
}
#[derive(Debug)]
pub struct BuildPkgError {
pub handler: pintc::error::Handler,
pub kind: BuildPkgErrorKind,
}
#[derive(Debug, Error)]
pub enum BuildPkgErrorKind {
#[error("`pintc` encountered an error: {0}")]
Pintc(#[from] PintcError),
#[error("failed to create lib providing contract and predicate CAs for {0:?}: {1}")]
ContractLibrary(String, std::io::Error),
}
#[derive(Debug, Error)]
pub enum PintcError {
#[error("parse error")]
Parse,
#[error("type check error")]
TypeCheck,
#[error("flattening error")]
Flatten,
#[error("abi gen")]
ABIGen,
#[error("asm-gen error")]
AsmGen,
}
#[derive(Debug, Error)]
pub enum WriteError {
#[error("failed to serialize contract or ABI: {0}")]
SerdeJson(#[from] serde_json::Error),
#[error("an I/O error occurred: {0}")]
Io(#[from] std::io::Error),
}
impl<'p> PlanBuilder<'p> {
pub fn next_pkg(&mut self) -> Option<PrebuiltPkg> {
let &n = self.order.next()?;
Some(PrebuiltPkg {
plan: self.plan,
built_pkgs: &mut self.built_pkgs,
n,
})
}
pub fn built_pkgs(&self) -> &BuiltPkgs {
&self.built_pkgs
}
pub fn build_all(mut self, skip_optimize: bool) -> Result<BuiltPkgs, BuildError> {
while let Some(prebuilt) = self.next_pkg() {
if let Err(pkg_err) = prebuilt.build(skip_optimize) {
let built_pkgs = self.built_pkgs;
return Err(BuildError {
built_pkgs,
pkg_err,
});
}
}
Ok(self.built_pkgs)
}
}
impl<'p, 'b> PrebuiltPkg<'p, 'b> {
pub fn node_ix(&self) -> NodeIx {
self.n
}
pub fn pinned(&self) -> &'p Pinned {
&self.plan.graph()[self.n]
}
pub fn build(self, skip_optimize: bool) -> Result<&'b BuiltPkg, BuildPkgError> {
let Self {
plan,
built_pkgs,
n,
} = self;
let built = build_pkg(plan, built_pkgs, n, skip_optimize)?;
built_pkgs.insert(n, built);
Ok(&built_pkgs[&n])
}
}
impl BuildPkgError {
pub fn print_diagnostics(self) {
let (errors, warnings) = self.handler.consume();
pintc::error::print_errors(&pintc::error::Errors(errors));
pintc::warning::print_warnings(&pintc::warning::Warnings(warnings));
}
}
impl BuiltPkg {
pub fn write_to_dir(&self, name: &str, path: &Path) -> Result<(), WriteError> {
match self {
Self::Library(_) => (),
Self::Contract(built) => {
let contract_string = serde_json::to_string_pretty(&built.contract)?;
let contract_path = path.join(name).with_extension("json");
std::fs::write(contract_path, contract_string)?;
let abi_string = serde_json::to_string_pretty(&built.abi)?;
let file_stem = format!("{}-abi", name);
let abi_path = path.join(file_stem).with_extension("json");
std::fs::write(abi_path, abi_string)?;
}
}
Ok(())
}
pub fn print_warnings(&self) {
let (Self::Contract(BuiltContract { warnings, .. })
| Self::Library(BuiltLibrary { warnings, .. })) = self;
pintc::warning::print_warnings(warnings);
}
}
fn dependencies<'a>(
n: NodeIx,
g: &Graph,
manifests: &'a PinnedManifests,
built_pkgs: &'a BuiltPkgs,
) -> HashMap<String, PathBuf> {
use petgraph::{visit::EdgeRef, Direction};
g.edges_directed(n, Direction::Outgoing)
.map(|e| {
let name = e.weight().name.to_string();
let dep_n = e.target();
let pinned = &g[dep_n];
let manifest = &manifests[&pinned.id()];
let entry_point = match &built_pkgs[&dep_n] {
BuiltPkg::Library(_lib) => manifest.entry_point(),
BuiltPkg::Contract(contract) => contract.lib_entry_point.clone(),
};
(name, entry_point)
})
.collect()
}
fn contract_dep_lib(
ca: &ContentAddress,
predicates: &[BuiltPredicate],
) -> std::io::Result<PathBuf> {
let temp_dir = std::env::temp_dir().join(format!("{:x}", ca));
std::fs::create_dir_all(&temp_dir)?;
let lib_str = format!("const ADDRESS: b256 = 0x{:x};", ca);
let lib_path = temp_dir.join("lib.pnt");
std::fs::write(&lib_path, lib_str.as_bytes())?;
for predicate in predicates {
let submod_str = format!("const ADDRESS: b256 = 0x{:x};", predicate.ca);
let mut submod: Vec<&str> = predicate.name.split("::").collect();
if matches!(&submod[..], &[""]) {
submod = vec!["root"];
}
let mut submod_path = temp_dir.clone();
submod_path.extend(submod.clone());
submod_path.set_extension("pnt");
std::fs::create_dir_all(submod_path.parent().expect("submod has no parent dir"))?;
std::fs::write(&submod_path, submod_str.as_bytes())?;
}
Ok(lib_path)
}
fn build_pkg(
plan: &Plan,
built_pkgs: &BuiltPkgs,
n: NodeIx,
skip_optimize: bool,
) -> Result<BuiltPkg, BuildPkgError> {
let graph = plan.graph();
let pinned = &graph[n];
let manifest = &plan.manifests()[&pinned.id()];
let entry_point = manifest.entry_point();
let handler = pintc::error::Handler::default();
let deps = dependencies(n, graph, plan.manifests(), built_pkgs);
let deps = deps
.iter()
.map(|(name, path)| (name.as_str(), path.as_path()))
.collect();
let Ok(parsed) = pintc::parser::parse_project(&handler, &deps, &entry_point) else {
let kind = BuildPkgErrorKind::from(PintcError::Parse);
return Err(BuildPkgError { handler, kind });
};
let Ok(contract) = handler.scope(|handler| parsed.type_check(handler)) else {
let kind = BuildPkgErrorKind::from(PintcError::TypeCheck);
return Err(BuildPkgError { handler, kind });
};
let built_pkg = match manifest.pkg.kind {
manifest::PackageKind::Library => {
let lib = BuiltLibrary {
warnings: pintc::warning::Warnings(handler.consume().1),
contract,
};
BuiltPkg::Library(lib)
}
manifest::PackageKind::Contract => {
let Ok(flattened) = handler.scope(|handler| contract.flatten(handler)) else {
let kind = BuildPkgErrorKind::from(PintcError::Flatten);
return Err(BuildPkgError { handler, kind });
};
let optimized = if skip_optimize {
flattened
} else {
flattened.optimize(&handler)
};
let Ok(abi) = optimized.abi(&handler) else {
let kind = BuildPkgErrorKind::from(PintcError::ABIGen);
return Err(BuildPkgError { handler, kind });
};
let Ok(contract) = handler.scope(|h| compile_contract(h, &optimized)) else {
let kind = BuildPkgErrorKind::from(PintcError::AsmGen);
return Err(BuildPkgError { handler, kind });
};
let predicates: Vec<_> = contract
.predicates
.into_iter()
.zip(contract.names)
.map(|(predicate, name)| {
let ca = essential_hash::content_addr(&predicate);
BuiltPredicate {
ca,
name,
predicate,
}
})
.collect();
let ca = essential_hash::contract_addr::from_predicate_addrs(
predicates.iter().map(|predicate| predicate.ca.clone()),
&contract.salt,
);
let lib_entry_point = match contract_dep_lib(&ca, &predicates) {
Ok(path) => path,
Err(e) => {
let kind = BuildPkgErrorKind::ContractLibrary(pinned.name.clone(), e);
return Err(BuildPkgError { handler, kind });
}
};
let (predicate_metadata, predicates) = predicates
.into_iter()
.map(
|BuiltPredicate {
ca,
name,
predicate,
}| (PredicateMetadata { ca, name }, predicate),
)
.unzip();
let contract = BuiltContract {
warnings: pintc::warning::Warnings(handler.consume().1),
ca,
predicate_metadata,
contract: Contract {
predicates,
salt: contract.salt,
},
lib_entry_point,
abi,
optimized,
};
BuiltPkg::Contract(contract)
}
};
Ok(built_pkg)
}
pub fn build_plan(plan: &Plan) -> PlanBuilder {
PlanBuilder {
built_pkgs: BuiltPkgs::default(),
plan,
order: plan.compilation_order().iter(),
}
}