essential_app_utils/
compile.rs1use 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}