1mod abi;
18mod natspec;
19
20use std::{
21 collections::HashMap,
22 fs::{
23 self,
24 File,
25 },
26 path::{
27 Path,
28 PathBuf,
29 },
30};
31
32use alloy_json_abi::JsonAbi;
33use anyhow::{
34 Context,
35 Result,
36};
37use cargo_metadata::TargetKind;
38use contract_metadata::{
39 CodeHash,
40 Contract,
41 Source,
42};
43use serde::{
44 Deserialize,
45 Serialize,
46};
47use serde_json::{
48 Map,
49 Value,
50};
51
52use self::natspec::{
53 DevDoc,
54 UserDoc,
55};
56use crate::{
57 CrateMetadata,
58 code_hash,
59};
60
61pub use self::abi::{
63 abi_path,
64 generate_abi,
65 write_abi,
66};
67
68#[derive(serde::Serialize, serde::Deserialize)]
70pub struct SolidityMetadataArtifacts {
71 pub dest_abi: PathBuf,
73 pub dest_metadata: PathBuf,
75}
76
77#[derive(Clone, Debug, Deserialize, Serialize)]
81pub struct SolidityContractMetadata {
82 pub compiler: Compiler,
84 pub language: String,
86 pub output: Output,
88 pub settings: Settings,
90 pub sources: HashMap<String, SourceFile>,
92 pub version: u8,
94}
95
96#[derive(Clone, Debug, Deserialize, Serialize)]
98pub struct Compiler {
99 #[serde(skip_serializing_if = "Option::is_none")]
101 #[serde(rename = "keccak256")]
102 pub hash: Option<CodeHash>,
103 pub version: String,
105}
106
107#[derive(Clone, Debug, Deserialize, Serialize)]
109pub struct Output {
110 pub abi: JsonAbi,
113 #[serde(rename = "devdoc")]
116 pub dev_doc: DevDoc,
117 #[serde(rename = "userdoc")]
120 pub user_doc: UserDoc,
121}
122
123#[derive(Clone, Debug, Deserialize, Serialize)]
129pub struct Settings {
130 pub ink: InkSettings,
132}
133
134#[derive(Clone, Debug, Deserialize, Serialize)]
136pub struct InkSettings {
137 pub hash: CodeHash,
139 pub image: Option<String>,
142 pub build_info: Option<Map<String, Value>>,
149}
150
151#[derive(Clone, Debug, Deserialize, Serialize)]
153pub struct SourceFile {
154 pub content: String,
156 #[serde(rename = "keccak256")]
158 pub hash: CodeHash,
159 #[serde(skip_serializing_if = "Option::is_none")]
161 pub license: Option<String>,
162}
163
164impl SourceFile {
165 pub fn new(content: String, license: Option<String>) -> Self {
167 let hash = code_hash(content.as_bytes());
168 Self {
169 hash: CodeHash::from(hash),
170 content,
171 license,
172 }
173 }
174}
175
176pub fn generate_metadata(
181 ink_project: &ink_metadata::sol::ContractMetadata,
182 abi: JsonAbi,
183 source: Source,
184 contract: Contract,
185 crate_metadata: &CrateMetadata,
186 image: Option<String>,
187) -> Result<SolidityContractMetadata> {
188 let sources = source_files(crate_metadata)?;
189 let (dev_doc, user_doc) = natspec::generate_natspec(ink_project, contract)?;
190 let metadata = SolidityContractMetadata {
191 compiler: Compiler {
192 hash: None,
193 version: source.compiler.to_string(),
194 },
195 language: source.language.to_string(),
196 output: Output {
197 abi,
198 dev_doc,
199 user_doc,
200 },
201 sources,
202 settings: Settings {
203 ink: InkSettings {
204 hash: source.hash,
205 image,
206 build_info: source.build_info,
207 },
208 },
209 version: 1,
210 };
211
212 Ok(metadata)
213}
214
215pub fn metadata_path(crate_metadata: &CrateMetadata) -> PathBuf {
217 let metadata_file = format!("{}.json", crate_metadata.contract_artifact_name);
218 crate_metadata.artifact_directory.join(metadata_file)
219}
220
221pub fn write_metadata<P>(metadata: &SolidityContractMetadata, path: P) -> Result<()>
225where
226 P: AsRef<Path>,
227{
228 let json = serde_json::to_string(metadata)?;
229 fs::write(path, json)?;
230
231 Ok(())
232}
233
234pub fn load_metadata<P>(metadata_path: P) -> Result<SolidityContractMetadata>
236where
237 P: AsRef<Path>,
238{
239 let path = metadata_path.as_ref();
240 let file = File::open(path)
241 .context(format!("Failed to open metadata file {}", path.display()))?;
242 serde_json::from_reader(file).context(format!(
243 "Failed to deserialize metadata file {}",
244 path.display()
245 ))
246}
247
248fn source_files(crate_metadata: &CrateMetadata) -> Result<HashMap<String, SourceFile>> {
250 let mut source_files = HashMap::new();
251
252 let manifest_path = &crate_metadata.manifest_path;
254 let project_dir = manifest_path.absolute_directory()?;
255 let manifest_path_buf = PathBuf::from(manifest_path.clone());
256 let manifest_key = manifest_path_buf
257 .strip_prefix(&project_dir)
258 .unwrap_or_else(|_| &manifest_path_buf)
259 .to_string_lossy()
260 .into_owned();
261 let manifest_content = fs::read_to_string(&manifest_path_buf)?;
262 source_files.insert(
263 manifest_key,
264 SourceFile::new(
265 manifest_content,
266 crate_metadata.root_package.license.clone(),
267 ),
268 );
269
270 let lib_src_path = &crate_metadata
272 .root_package
273 .targets
274 .iter()
275 .find_map(|target| {
276 (target.kind == [TargetKind::Lib]).then_some(target.src_path.clone())
277 })
278 .context("Couldn't find `lib.rs` path")?;
279 let lib_src_content = fs::read_to_string(lib_src_path)?;
280 let lib_src_key = lib_src_path
281 .strip_prefix(&project_dir)
282 .unwrap_or_else(|_| lib_src_path)
283 .to_string();
284 source_files.insert(
285 lib_src_key,
286 SourceFile::new(lib_src_content, crate_metadata.root_package.license.clone()),
287 );
288
289 Ok(source_files)
290}