1use crate::{
4 manifest,
5 plan::{Graph, NodeIx, Pinned, PinnedManifests, Plan},
6};
7use clap::builder::styling::Style;
8use essential_types::{
9 contract::Contract,
10 predicate::{Predicate as CompiledPredicate, Program},
11 ContentAddress,
12};
13use pint_abi_types::ContractABI;
14use pintc::asm_gen::compile_contract;
15use std::{
16 collections::{BTreeSet, HashMap},
17 path::{Path, PathBuf},
18};
19use thiserror::Error;
20
21pub struct PlanBuilder<'p> {
23 pub plan: &'p Plan,
25 built_pkgs: BuiltPkgs,
26 order: std::slice::Iter<'p, NodeIx>,
27}
28
29pub struct PrebuiltPkg<'p, 'b> {
31 pub plan: &'p Plan,
32 built_pkgs: &'b mut BuiltPkgs,
33 n: NodeIx,
35}
36
37#[derive(Default)]
38pub struct BuildOptions {
39 pub salts: HashMap<manifest::ManifestFile, [u8; 32]>,
47 pub print_parsed: bool,
49 pub print_flat: bool,
51 pub print_optimized: bool,
53 pub print_asm: bool,
55 pub skip_optimize: bool,
57}
58
59pub type BuiltPkgs = HashMap<NodeIx, BuiltPkg>;
61
62#[derive(Debug)]
64pub enum BuiltPkg {
65 Contract(BuiltContract),
67 Library(BuiltLibrary),
69}
70
71#[derive(Debug)]
73pub struct BuiltContract {
74 pub warnings: pintc::warning::Warnings,
76 pub predicate_metadata: Vec<PredicateMetadata>,
78 pub contract: Contract,
80 pub programs: BTreeSet<Program>,
82 pub ca: ContentAddress,
84 pub lib_entry_point: PathBuf,
86 pub abi: ContractABI,
88 pub optimized: pintc::predicate::Contract,
90}
91
92#[derive(Debug)]
94pub struct BuiltPredicate {
95 pub ca: ContentAddress,
97 pub name: String,
99 pub predicate: CompiledPredicate,
100}
101
102#[derive(Debug)]
104pub struct PredicateMetadata {
105 pub ca: ContentAddress,
107 pub name: String,
109}
110
111#[derive(Debug)]
113pub struct BuiltLibrary {
114 pub warnings: pintc::warning::Warnings,
116 pub contract: pintc::predicate::Contract,
118}
119
120#[derive(Debug)]
122pub struct BuildError {
123 pub built_pkgs: BuiltPkgs,
125 pub pkg_err: BuildPkgError,
127}
128
129#[derive(Debug)]
131pub struct BuildPkgError {
132 pub handler: pintc::error::Handler,
134 pub kind: BuildPkgErrorKind,
136}
137
138#[derive(Debug, Error)]
139pub enum BuildPkgErrorKind {
140 #[error("`pintc` encountered an error: {0}")]
141 Pintc(#[from] PintcError),
142 #[error("failed to create lib providing contract and predicate CAs for {0:?}: {1}")]
143 ContractLibrary(String, std::io::Error),
144}
145
146#[derive(Debug, Error)]
147pub enum PintcError {
148 #[error("parse error")]
149 Parse,
150 #[error("type check error")]
151 TypeCheck,
152 #[error("flattening error")]
153 Flatten,
154 #[error("abi gen")]
155 ABIGen,
156 #[error("asm-gen error")]
157 AsmGen,
158}
159
160#[derive(Debug, Error)]
162pub enum WriteError {
163 #[error("failed to serialize contract or ABI: {0}")]
165 SerdeJson(#[from] serde_json::Error),
166 #[error("an I/O error occurred: {0}")]
168 Io(#[from] std::io::Error),
169}
170
171impl PlanBuilder<'_> {
172 pub fn next_pkg(&mut self) -> Option<PrebuiltPkg> {
174 let &n = self.order.next()?;
175 Some(PrebuiltPkg {
176 plan: self.plan,
177 built_pkgs: &mut self.built_pkgs,
178 n,
179 })
180 }
181
182 pub fn built_pkgs(&self) -> &BuiltPkgs {
184 &self.built_pkgs
185 }
186
187 #[allow(clippy::result_large_err)]
189 pub fn build_all(mut self, options: &BuildOptions) -> Result<BuiltPkgs, BuildError> {
190 while let Some(prebuilt) = self.next_pkg() {
191 if let Err(pkg_err) = prebuilt.build(options) {
192 let built_pkgs = self.built_pkgs;
193 return Err(BuildError {
194 built_pkgs,
195 pkg_err,
196 });
197 }
198 }
199 Ok(self.built_pkgs)
200 }
201
202 pub fn into_built_pkgs(self) -> BuiltPkgs {
204 self.built_pkgs
205 }
206}
207
208impl<'p, 'b> PrebuiltPkg<'p, 'b> {
209 pub fn node_ix(&self) -> NodeIx {
211 self.n
212 }
213
214 pub fn pinned(&self) -> &'p Pinned {
216 &self.plan.graph()[self.n]
217 }
218
219 pub fn build(self, options: &BuildOptions) -> Result<&'b BuiltPkg, BuildPkgError> {
221 let Self {
222 plan,
223 built_pkgs,
224 n,
225 } = self;
226 let built = build_pkg(plan, built_pkgs, n, options)?;
227 built_pkgs.insert(n, built);
228 Ok(&built_pkgs[&n])
229 }
230}
231
232impl BuildPkgError {
233 pub fn print_diagnostics(self) {
235 let (errors, warnings) = self.handler.consume();
236 pintc::error::print_errors(&pintc::error::Errors(errors));
237 pintc::warning::print_warnings(&pintc::warning::Warnings(warnings));
238 }
239}
240
241impl BuiltPkg {
242 pub fn write_to_dir(&self, name: &str, path: &Path) -> Result<(), WriteError> {
244 match self {
245 Self::Library(_) => (),
246 Self::Contract(built) => {
247 let contract_string =
249 serde_json::to_string_pretty(&(&built.contract, &built.programs))?;
250 let contract_path = path.join(name).with_extension("json");
251 std::fs::write(contract_path, contract_string)?;
252
253 let abi_string = serde_json::to_string_pretty(&built.abi)?;
255 let file_stem = format!("{}-abi", name);
256 let abi_path = path.join(file_stem).with_extension("json");
257 std::fs::write(abi_path, abi_string)?;
258 }
259 }
260 Ok(())
261 }
262
263 pub fn print_warnings(&self) {
265 let (Self::Contract(BuiltContract { warnings, .. })
266 | Self::Library(BuiltLibrary { warnings, .. })) = self;
267
268 pintc::warning::print_warnings(warnings);
269 }
270}
271
272fn dependencies<'a>(
274 n: NodeIx,
275 g: &Graph,
276 manifests: &'a PinnedManifests,
277 built_pkgs: &'a BuiltPkgs,
278) -> HashMap<String, PathBuf> {
279 use petgraph::{visit::EdgeRef, Direction};
280 g.edges_directed(n, Direction::Outgoing)
281 .map(|e| {
282 let name = e.weight().name.to_string();
283 let dep_n = e.target();
284 let pinned = &g[dep_n];
285 let manifest = &manifests[&pinned.id()];
286 let entry_point = match &built_pkgs[&dep_n] {
287 BuiltPkg::Library(_lib) => manifest.entry_point(),
288 BuiltPkg::Contract(contract) => contract.lib_entry_point.clone(),
289 };
290 (name, entry_point)
291 })
292 .collect()
293}
294
295fn contract_dep_lib(
301 ca: &ContentAddress,
302 predicates: &[BuiltPredicate],
303) -> std::io::Result<PathBuf> {
304 let temp_dir = std::env::temp_dir().join(format!("{:x}", ca));
306 std::fs::create_dir_all(&temp_dir)?;
307
308 let lib_str = format!("const ADDRESS: b256 = 0x{:x};", ca);
310 let lib_path = temp_dir.join("lib.pnt");
311 std::fs::write(&lib_path, lib_str.as_bytes())?;
312
313 for predicate in predicates {
315 let submod_str = format!("const ADDRESS: b256 = 0x{:x};", predicate.ca);
316
317 let mut submod: Vec<&str> = predicate.name.split("::").collect();
319 if matches!(&submod[..], &[""]) {
321 submod = vec!["root"];
322 }
323 let mut submod_path = temp_dir.clone();
324 submod_path.extend(submod.clone());
325 submod_path.set_extension("pnt");
326 std::fs::create_dir_all(submod_path.parent().expect("submod has no parent dir"))?;
327 std::fs::write(&submod_path, submod_str.as_bytes())?;
328 }
329
330 Ok(lib_path)
331}
332
333fn build_pkg(
335 plan: &Plan,
336 built_pkgs: &BuiltPkgs,
337 n: NodeIx,
338 options: &BuildOptions,
339) -> Result<BuiltPkg, BuildPkgError> {
340 let graph = plan.graph();
341 let pinned = &graph[n];
342 let manifest = &plan.manifests()[&pinned.id()];
343 let entry_point = manifest.entry_point();
344 let handler = pintc::error::Handler::default();
345 let deps = dependencies(n, graph, plan.manifests(), built_pkgs);
346 let source_str = match pinned.source {
347 crate::source::Pinned::Member(_) => {
348 format!("{}", manifest.dir().display())
349 }
350 _ => format!("{}", pinned.source),
351 };
352
353 let deps = deps
355 .iter()
356 .map(|(name, path)| (name.as_str(), path.as_path()))
357 .collect();
358 let Ok(parsed) = pintc::parser::parse_project(&handler, &deps, &entry_point) else {
359 let kind = BuildPkgErrorKind::from(PintcError::Parse);
360 return Err(BuildPkgError { handler, kind });
361 };
362
363 let bold = Style::new().bold();
364 if options.print_parsed {
365 println!(
366 " {}Printing parsed{} {} [{}] ({})",
367 bold.render(),
368 bold.render_reset(),
369 pinned.name,
370 manifest.pkg.kind,
371 source_str,
372 );
373 println!("\n{parsed}");
374 }
375
376 let Ok(contract) = handler.scope(|handler| parsed.type_check(handler)) else {
378 let kind = BuildPkgErrorKind::from(PintcError::TypeCheck);
379 return Err(BuildPkgError { handler, kind });
380 };
381
382 let built_pkg = match manifest.pkg.kind {
383 manifest::PackageKind::Library => {
384 let lib = BuiltLibrary {
387 warnings: pintc::warning::Warnings(handler.consume().1),
388 contract,
389 };
390 BuiltPkg::Library(lib)
391 }
392 manifest::PackageKind::Contract => {
393 let Ok(flattened) = handler.scope(|handler| contract.flatten(handler)) else {
395 let kind = BuildPkgErrorKind::from(PintcError::Flatten);
396 return Err(BuildPkgError { handler, kind });
397 };
398
399 if options.print_flat {
400 println!(
401 " {}Printing flattened{} {} [{}] ({})",
402 bold.render(),
403 bold.render_reset(),
404 pinned.name,
405 manifest.pkg.kind,
406 source_str,
407 );
408 println!("\n{flattened}");
409 }
410
411 let optimized = if options.skip_optimize {
413 flattened
414 } else {
415 flattened.optimize(&handler)
416 };
417
418 if options.print_optimized {
419 println!(
420 " {}Printing optimized{} {} [{}] ({})",
421 bold.render(),
422 bold.render_reset(),
423 pinned.name,
424 manifest.pkg.kind,
425 source_str,
426 );
427 println!("\n{optimized}");
428 }
429
430 let Ok(abi) = optimized.abi(&handler) else {
432 let kind = BuildPkgErrorKind::from(PintcError::ABIGen);
433 return Err(BuildPkgError { handler, kind });
434 };
435
436 let Ok(contract) = handler.scope(|h| {
438 compile_contract(
439 h,
440 *options.salts.get(manifest).unwrap_or(&[0; 32]),
441 &optimized,
442 )
443 }) else {
444 let kind = BuildPkgErrorKind::from(PintcError::AsmGen);
445 return Err(BuildPkgError { handler, kind });
446 };
447
448 if options.print_asm {
449 println!(
450 " {}Printing assembly for{} {} [{}] ({})",
451 bold.render(),
452 bold.render_reset(),
453 pinned.name,
454 manifest.pkg.kind,
455 source_str,
456 );
457 println!("\n{contract}");
458 }
459
460 let predicates: Vec<_> = contract
462 .contract
463 .predicates
464 .clone()
465 .into_iter()
466 .zip(contract.names)
467 .map(|(predicate, name)| {
468 let ca = essential_hash::content_addr(&predicate);
469 BuiltPredicate {
470 ca,
471 name,
472 predicate,
473 }
474 })
475 .collect();
476
477 let ca = essential_hash::contract_addr::from_predicate_addrs(
479 predicates.iter().map(|predicate| predicate.ca.clone()),
480 &contract.contract.salt,
481 );
482
483 let lib_entry_point = match contract_dep_lib(&ca, &predicates) {
485 Ok(path) => path,
486 Err(e) => {
487 let kind = BuildPkgErrorKind::ContractLibrary(pinned.name.clone(), e);
488 return Err(BuildPkgError { handler, kind });
489 }
490 };
491
492 let predicate_metadata = predicates
493 .into_iter()
494 .map(|BuiltPredicate { ca, name, .. }| PredicateMetadata { ca, name })
495 .collect::<Vec<_>>();
496
497 let contract = BuiltContract {
498 warnings: pintc::warning::Warnings(handler.consume().1),
499 ca,
500 predicate_metadata,
501 contract: contract.contract,
502 programs: contract.programs,
503 lib_entry_point,
504 abi,
505 optimized,
506 };
507 BuiltPkg::Contract(contract)
508 }
509 };
510
511 Ok(built_pkg)
512}
513
514pub fn build_plan(plan: &Plan) -> PlanBuilder {
517 PlanBuilder {
518 built_pkgs: BuiltPkgs::default(),
519 plan,
520 order: plan.compilation_order().iter(),
521 }
522}