essential_app_utils/
compile.rs

1use anyhow::{bail, ensure};
2use essential_types::{
3    contract::Contract,
4    predicate::{Predicate, Program},
5};
6use std::path::PathBuf;
7use tokio::{
8    io::{AsyncReadExt, BufReader},
9    process::Command,
10};
11
12#[derive(Debug)]
13pub struct NamedContracts {
14    pub contracts: Vec<NamedContract>,
15}
16
17#[derive(Debug)]
18pub struct NamedContract {
19    pub name: String,
20    pub contract: Contract,
21    pub predicates: Vec<String>,
22    pub source: String,
23}
24
25pub async fn compile_pint_project(path: PathBuf) -> anyhow::Result<(Contract, Vec<Program>)> {
26    let (bytes, _, _) = compile_pint_project_inner(path, false).await?;
27    let (contract, programs): (Contract, Vec<Program>) = serde_json::from_slice(&bytes)?;
28    Ok((contract, programs))
29}
30
31pub async fn compile_pint_project_and_abi(
32    path: PathBuf,
33) -> anyhow::Result<(Contract, serde_json::Value)> {
34    let (bytes, abi, _) = compile_pint_project_inner(path, false).await?;
35    let contract: Contract = serde_json::from_slice(&bytes)?;
36    let abi: serde_json::Value = serde_json::from_slice(&abi)?;
37    Ok((contract, abi))
38}
39
40pub async fn compile_pint_project_and_abi_with_source(
41    path: PathBuf,
42) -> anyhow::Result<(Contract, serde_json::Value, String)> {
43    let (bytes, abi, source) = compile_pint_project_inner(path, true).await?;
44    let contract: Contract = serde_json::from_slice(&bytes)?;
45    let abi: serde_json::Value = serde_json::from_slice(&abi)?;
46    Ok((contract, abi, source))
47}
48
49pub async fn compile_pint_project_inner(
50    path: PathBuf,
51    include_source: bool,
52) -> anyhow::Result<(Vec<u8>, Vec<u8>, String)> {
53    let pint_manifest_path = path.join("pint.toml");
54    assert!(
55        pint_manifest_path.exists(),
56        "pint.toml not found: {:?}",
57        pint_manifest_path
58    );
59
60    let pint_toml = tokio::fs::read_to_string(&pint_manifest_path).await?;
61    let pint_toml = pint_toml.parse::<toml::Table>()?;
62    let Some(name) = pint_toml
63        .get("package")
64        .and_then(|p| p.as_table()?.get("name"))
65        .and_then(|name| name.as_str())
66    else {
67        bail!("name not found in pint.toml")
68    };
69
70    let output = if include_source {
71        Command::new("pint")
72            .arg("build")
73            .arg("--manifest-path")
74            .arg(pint_manifest_path.display().to_string())
75            .arg("--silent")
76            .arg("--print-flat")
77            .output()
78            .await?
79    } else {
80        Command::new("pint")
81            .arg("build")
82            .arg("--manifest-path")
83            .arg(pint_manifest_path.display().to_string())
84            .output()
85            .await?
86    };
87
88    ensure!(
89        output.status.success(),
90        "pint failed: {}",
91        String::from_utf8_lossy(&output.stderr)
92    );
93
94    let source = if include_source {
95        let s = String::from_utf8_lossy(&output.stdout);
96        s.lines()
97            .skip_while(|line| !line.trim().starts_with(&(format!("\u{1b}[1m{}", name))))
98            .skip(1)
99            .fold(String::new(), |acc, line| acc + line + "\n")
100    } else {
101        String::new()
102    };
103
104    let file = tokio::fs::File::open(
105        path.join("out")
106            .join("debug")
107            .join(format!("{}.json", name)),
108    )
109    .await?;
110    let mut bytes = Vec::new();
111    let mut reader = BufReader::new(file);
112    reader.read_to_end(&mut bytes).await?;
113
114    let abi_file = tokio::fs::File::open(
115        path.join("out")
116            .join("debug")
117            .join(format!("{}-abi.json", name)),
118    )
119    .await?;
120    let mut abi_bytes = Vec::new();
121    let mut reader = BufReader::new(abi_file);
122    reader.read_to_end(&mut abi_bytes).await?;
123
124    Ok((bytes, abi_bytes, source))
125}
126
127pub async fn get_contracts(
128    pint_directory: PathBuf,
129    contracts: &[&str],
130) -> anyhow::Result<NamedContracts> {
131    let mut out = Vec::with_capacity(contracts.len());
132
133    for name in contracts {
134        let (contract, abi, source) =
135            compile_pint_project_and_abi_with_source(pint_directory.clone().join(name)).await?;
136        let predicate_names = abi["predicates"]
137            .as_array()
138            .unwrap()
139            .iter()
140            .filter(|predicate| !predicate["name"].as_str().unwrap().is_empty())
141            .map(|predicate| predicate["name"].as_str().unwrap().to_string())
142            .collect();
143        let contract = NamedContract {
144            name: name.to_string(),
145            contract,
146            predicates: predicate_names,
147            source,
148        };
149        out.push(contract);
150    }
151    Ok(NamedContracts { contracts: out })
152}
153
154impl NamedContracts {
155    pub fn get_contract(&self, name: &str) -> Option<&NamedContract> {
156        self.contracts.iter().find(|contract| contract.name == name)
157    }
158}
159
160impl NamedContract {
161    pub fn get_predicate(&self, name: &str) -> Option<&Predicate> {
162        self.predicates
163            .iter()
164            .position(|predicate| {
165                predicate.trim().trim_start_matches("::").to_lowercase()
166                    == name.trim().trim_start_matches("::").to_lowercase()
167            })
168            .and_then(|pos| self.contract.predicates.get(pos))
169    }
170}