libid_contracts/
artifacts.rs1use std::{
11 collections::BTreeMap,
12 path::PathBuf,
13};
14
15use alloy::{
16 hex,
17 primitives::Bytes,
18 providers::Provider,
19};
20use include_dir::{
21 include_dir,
22 Dir,
23};
24
25use crate::error::{
26 Error,
27 Result,
28};
29
30static EMBEDDED: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/artifacts");
32
33pub const COVERED: &[(&str, &str)] = &[
37 ("NotaryService", "NotaryService"),
39 ("CeremonyProofVerifier", "CeremonyProofVerifier"),
40 ("ERC1967Proxy", "ERC1967Proxy"),
41 ("GoogleJwtRoots", "GoogleJwtRoots"),
42 ("IdentityNames", "IdentityNames"),
44 ("HandleResolver", "HandleResolver"),
46 ("LibidFactory", "LibidFactory"),
48 ("WTIA9", "WTIA9"),
49];
50
51enum Source {
52 Embedded,
53 Dir(PathBuf),
54}
55
56pub struct Artifacts {
58 source: Source,
59}
60
61impl Artifacts {
62 pub const fn embedded() -> Self {
65 Self {
66 source: Source::Embedded,
67 }
68 }
69
70 pub fn from_dir(dir: impl Into<PathBuf>) -> Self {
73 Self {
74 source: Source::Dir(dir.into()),
75 }
76 }
77
78 pub fn raw(&self, file: &str, contract: &str) -> Result<serde_json::Value> {
80 let rel = format!("{file}.sol/{contract}.json");
81 let contents = match &self.source {
82 Source::Embedded => EMBEDDED
83 .get_file(&rel)
84 .and_then(|f| f.contents_utf8())
85 .map(str::to_owned)
86 .ok_or_else(|| Error::Artifact {
87 detail: format!("no embedded artifact {rel}"),
88 })?,
89 Source::Dir(dir) => {
90 let path = dir.join(&rel);
91 std::fs::read_to_string(&path).map_err(|e| Error::Artifact {
92 detail: format!("failed to read artifact {}: {e}", path.display()),
93 })?
94 }
95 };
96 serde_json::from_str(&contents).map_err(|e| Error::Artifact {
97 detail: format!("failed to parse artifact {rel}: {e}"),
98 })
99 }
100
101 pub fn bytecode(&self, contract: &str) -> Result<Bytes> {
105 self.bytecode_named(contract, contract)
106 }
107
108 pub fn bytecode_named(&self, file: &str, contract: &str) -> Result<Bytes> {
111 let hex_str = self.bytecode_hex(file, contract)?;
112 if hex_str.contains("__$") {
113 return Err(Error::Artifact {
114 detail: format!(
115 "{file}.sol:{contract} has unresolved link references; deploy it \
116 via linked_bytecode"
117 ),
118 });
119 }
120 let bytes = hex::decode(&hex_str).map_err(|e| Error::Artifact {
121 detail: format!("invalid bytecode hex for {file}.sol:{contract}: {e}"),
122 })?;
123 Ok(Bytes::from(bytes))
124 }
125
126 pub async fn linked_bytecode<P: Provider>(
137 &self,
138 provider: &P,
139 file: &str,
140 contract: &str,
141 sender: Option<alloy::primitives::Address>,
142 ) -> Result<Bytes> {
143 crate::deploy::load_linked_bytecode(provider, self, file, contract, sender).await
144 }
145
146 pub fn method_identifiers(&self, contract: &str) -> Result<BTreeMap<String, String>> {
149 let json = self.raw(contract, contract)?;
150 let methods =
151 json["methodIdentifiers"]
152 .as_object()
153 .ok_or_else(|| Error::Artifact {
154 detail: format!(
155 "no methodIdentifiers in {contract}.sol/{contract}.json"
156 ),
157 })?;
158 methods
159 .iter()
160 .map(|(sig, value)| {
161 let sel = value.as_str().ok_or_else(|| Error::Artifact {
162 detail: format!("non-string selector for {sig} in {contract}"),
163 })?;
164 Ok((sig.clone(), sel.to_owned()))
165 })
166 .collect()
167 }
168
169 pub(crate) fn bytecode_hex(&self, file: &str, contract: &str) -> Result<String> {
171 let json = self.raw(file, contract)?;
172 let raw = json["bytecode"]["object"]
173 .as_str()
174 .ok_or_else(|| Error::Artifact {
175 detail: format!("no bytecode.object in {file}.sol/{contract}.json"),
176 })?;
177 Ok(raw.strip_prefix("0x").unwrap_or(raw).to_owned())
178 }
179
180 pub(crate) fn link_references(
182 &self,
183 file: &str,
184 contract: &str,
185 ) -> Result<serde_json::Map<String, serde_json::Value>> {
186 let json = self.raw(file, contract)?;
187 Ok(json["bytecode"]["linkReferences"]
188 .as_object()
189 .cloned()
190 .unwrap_or_default())
191 }
192}
193
194impl Default for Artifacts {
195 fn default() -> Self {
196 Self::embedded()
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203
204 #[test]
208 fn every_covered_contract_has_bytecode() {
209 let artifacts = Artifacts::embedded();
210 for &(file, contract) in COVERED {
211 let hex_str = artifacts
212 .bytecode_hex(file, contract)
213 .unwrap_or_else(|e| panic!("{file}.sol:{contract}: {e}"));
214 assert!(
215 !hex_str.is_empty(),
216 "{file}.sol:{contract} has empty bytecode"
217 );
218 }
219 }
220
221 #[test]
225 fn unlinked_contracts_decode() {
226 let artifacts = Artifacts::embedded();
227 for &(file, contract) in COVERED {
228 if artifacts
229 .link_references(file, contract)
230 .unwrap()
231 .is_empty()
232 {
233 let bytecode = artifacts
234 .bytecode_named(file, contract)
235 .unwrap_or_else(|e| panic!("{file}.sol:{contract}: {e}"));
236 assert!(!bytecode.is_empty());
237 }
238 }
239 }
240}