ethers_solc/
buildinfo.rs

1//! Represents an entire build
2
3use crate::{utils, CompilerInput, CompilerOutput, SolcError};
4use md5::Digest;
5use semver::Version;
6use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer};
7use std::{cell::RefCell, path::Path, rc::Rc};
8
9pub const ETHERS_FORMAT_VERSION: &str = "ethers-rs-sol-build-info-1";
10
11// A hardhat compatible build info representation
12#[derive(Serialize, Deserialize)]
13#[serde(rename_all = "camelCase")]
14pub struct BuildInfo {
15    pub id: String,
16    #[serde(rename = "_format")]
17    pub format: String,
18    pub solc_version: Version,
19    pub solc_long_version: Version,
20    pub input: CompilerInput,
21    pub output: CompilerOutput,
22}
23
24impl BuildInfo {
25    /// Deserializes the `BuildInfo` object from the given file
26    pub fn read(path: impl AsRef<Path>) -> Result<Self, SolcError> {
27        utils::read_json_file(path)
28    }
29}
30
31/// Represents `BuildInfo` object
32#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
33pub struct RawBuildInfo {
34    /// The hash that identifies the BuildInfo
35    pub id: String,
36    /// serialized `BuildInfo` json
37    pub build_info: String,
38}
39
40// === impl RawBuildInfo ===
41
42impl RawBuildInfo {
43    /// Serializes a `BuildInfo` object
44    pub fn new(
45        input: &CompilerInput,
46        output: &CompilerOutput,
47        version: &Version,
48    ) -> serde_json::Result<RawBuildInfo> {
49        let mut hasher = md5::Md5::new();
50        let w = BuildInfoWriter { buf: Rc::new(RefCell::new(Vec::with_capacity(128))) };
51        let mut buf = w.clone();
52        let mut serializer = serde_json::Serializer::pretty(&mut buf);
53        let mut s = serializer.serialize_struct("BuildInfo", 6)?;
54        s.serialize_field("_format", &ETHERS_FORMAT_VERSION)?;
55        let solc_short = format!("{}.{}.{}", version.major, version.minor, version.patch);
56        s.serialize_field("solcVersion", &solc_short)?;
57        s.serialize_field("solcLongVersion", &version)?;
58        s.serialize_field("input", input)?;
59
60        // create the hash for `{_format,solcVersion,solcLongVersion,input}`
61        // N.B. this is not exactly the same as hashing the json representation of these values but
62        // the must efficient one
63        hasher.update(&*w.buf.borrow());
64        let result = hasher.finalize();
65        let id = hex::encode(result);
66
67        s.serialize_field("id", &id)?;
68        s.serialize_field("output", output)?;
69        s.end()?;
70
71        drop(buf);
72
73        let build_info = unsafe {
74            // serde_json does not emit non UTF8
75            String::from_utf8_unchecked(w.buf.take())
76        };
77
78        Ok(RawBuildInfo { id, build_info })
79    }
80}
81
82#[derive(Clone)]
83struct BuildInfoWriter {
84    buf: Rc<RefCell<Vec<u8>>>,
85}
86
87impl std::io::Write for BuildInfoWriter {
88    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
89        self.buf.borrow_mut().write(buf)
90    }
91
92    fn flush(&mut self) -> std::io::Result<()> {
93        self.buf.borrow_mut().flush()
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100    use crate::Source;
101    use std::{collections::BTreeMap, path::PathBuf};
102
103    #[test]
104    fn build_info_serde() {
105        let inputs = CompilerInput::with_sources(BTreeMap::from([(
106            PathBuf::from("input.sol"),
107            Source::new(""),
108        )]));
109        let output = CompilerOutput::default();
110        let v: Version = "0.8.4+commit.c7e474f2".parse().unwrap();
111        let raw_info = RawBuildInfo::new(&inputs[0], &output, &v).unwrap();
112        let _info: BuildInfo = serde_json::from_str(&raw_info.build_info).unwrap();
113    }
114}