rvm/
releases.rs

1use std::{
2    collections::BTreeMap,
3    path::{Path, PathBuf},
4    time::Duration,
5};
6
7use semver::{Comparator, Prerelease, Version};
8use serde::{Deserialize, Serialize};
9use sha2::Digest;
10use url::Url;
11
12use crate::{constants::MIN_VERSION, errors::Error};
13
14/// Resolc equivalent of `list.json` of `solc` releases.
15#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
16pub struct Releases {
17    pub(crate) builds: Vec<Build>,
18    pub(crate) releases: BTreeMap<Version, String>,
19    #[serde(rename = "latestRelease")]
20    pub(crate) latest_release: Version,
21}
22
23impl Releases {
24    /// Grabs all releases from the remote `url`.
25    pub fn new(url: url::Url) -> Result<Releases, Error> {
26        reqwest::blocking::get(url)?.json().map_err(Into::into)
27    }
28
29    pub fn merge(&mut self, other: &mut Self) {
30        // merge builds with nightly
31        self.builds.extend_from_slice(&other.builds);
32        self.builds.dedup_by_key(|i| i.long_version.clone());
33
34        // merge releases with nightly
35        self.releases.append(&mut other.releases);
36
37        // Note latest nightly is not set as latest release.
38    }
39
40    /// Returns a build by Resolc version if it's present
41    pub fn get_build(&self, version: &Version) -> Result<&Build, Error> {
42        self.releases
43            .get(version)
44            .and_then(|_| self.builds.iter().find(|item| item.version == *version))
45            .ok_or_else(|| Error::UnknownVersion {
46                version: version.clone(),
47            })
48    }
49}
50
51#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
52/// Basic information about Resolc binary
53pub struct BinaryInfo {
54    /// Resolc version
55    pub version: Version,
56    /// first supported `solc` version
57    pub first_supported_solc_version: Version,
58    /// last supported `solc` version
59    pub last_supported_solc_version: Version,
60}
61
62#[derive(PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
63/// Basic information about Resolc binary including whether or not it's already installed
64pub enum Binary {
65    /// Resolc binaries that are installed locally
66    Local {
67        /// Path to the installed binary
68        path: PathBuf,
69        /// Basic info about Resolc library
70        info: BinaryInfo,
71    },
72    /// Resolc binaries that are available and can be downloaded
73    Remote(BinaryInfo),
74}
75
76impl std::fmt::Debug for Binary {
77    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78        match self {
79            Binary::Local { path, info } => f
80                .debug_struct("Installed")
81                .field("path", path)
82                .field("version", &info.version.to_string())
83                .field(
84                    "solc_req",
85                    &semver::VersionReq {
86                        comparators: vec![
87                            Comparator {
88                                op: semver::Op::GreaterEq,
89                                major: info.first_supported_solc_version.major,
90                                minor: Some(info.first_supported_solc_version.minor),
91                                patch: Some(info.first_supported_solc_version.patch),
92                                pre: Prerelease::default(),
93                            },
94                            Comparator {
95                                op: semver::Op::LessEq,
96                                major: info.last_supported_solc_version.major,
97                                minor: Some(info.last_supported_solc_version.minor),
98                                patch: Some(info.last_supported_solc_version.patch),
99                                pre: Prerelease::default(),
100                            },
101                        ],
102                    }
103                    .to_string(),
104                )
105                .finish(),
106            Binary::Remote(info) => f
107                .debug_struct("Remote")
108                .field("version", &info.version.to_string())
109                .field(
110                    "solc_req",
111                    &semver::VersionReq {
112                        comparators: vec![
113                            Comparator {
114                                op: semver::Op::GreaterEq,
115                                major: info.first_supported_solc_version.major,
116                                minor: Some(info.first_supported_solc_version.minor),
117                                patch: Some(info.first_supported_solc_version.patch),
118                                pre: Prerelease::default(),
119                            },
120                            Comparator {
121                                op: semver::Op::LessEq,
122                                major: info.last_supported_solc_version.major,
123                                minor: Some(info.last_supported_solc_version.minor),
124                                patch: Some(info.last_supported_solc_version.patch),
125                                pre: Prerelease::default(),
126                            },
127                        ],
128                    }
129                    .to_string(),
130                )
131                .finish(),
132        }
133    }
134}
135
136impl Binary {
137    /// Returns the version for the given `Binary`
138    pub fn version(&self) -> &Version {
139        match self {
140            Binary::Local { info, .. } => &info.version,
141            Binary::Remote(info) => &info.version,
142        }
143    }
144    /// Returns the path for the given `Binary`
145    pub fn local(&self) -> Option<&Path> {
146        match self {
147            Binary::Local { path, .. } => Some(path.as_ref()),
148            Binary::Remote(_) => None,
149        }
150    }
151}
152
153/// Basic information about Resolc build that is available to be installed
154#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
155pub struct Build {
156    pub(crate) name: String,
157    pub(crate) version: Version,
158    #[serde(rename = "longVersion")]
159    pub(crate) long_version: String,
160    pub(crate) url: Url,
161    #[serde(rename = "firstSolcVersion")]
162    pub(crate) first_supported_solc_version: Version,
163    #[serde(rename = "lastSolcVersion")]
164    pub(crate) last_supported_solc_version: Version,
165    pub(crate) sha256: String,
166}
167
168impl Build {
169    fn verify_binary(&self, bin: &[u8]) -> Result<(), Error> {
170        let checksum = hex::decode(&self.sha256)?;
171        let checksum_from_binary = {
172            let mut hasher: sha2::Sha256 = Digest::new();
173            hasher.update(bin);
174            hasher.finalize()
175        };
176        if checksum == checksum_from_binary[..] {
177            Ok(())
178        } else {
179            Err(Error::ChecksumValidationError {
180                expected: self.sha256.clone(),
181                actual: hex::encode(checksum_from_binary),
182            })
183        }
184    }
185    /// Checks compatibility between selected Resolc and `solc` versions
186    ///
187    /// # Arguments
188    ///
189    /// * `solc_version` -  `solc` version requirement this will allow check the compatibility between the two compiler versions
190    pub fn check_solc_compat(&self, solc_version: &Version) -> Result<(), Error> {
191        let version_req = semver::VersionReq {
192            comparators: vec![
193                Comparator {
194                    op: semver::Op::GreaterEq,
195                    major: self.first_supported_solc_version.major,
196                    minor: Some(self.first_supported_solc_version.minor),
197                    patch: Some(self.first_supported_solc_version.patch),
198                    pre: Prerelease::default(),
199                },
200                Comparator {
201                    op: semver::Op::LessEq,
202                    major: self.last_supported_solc_version.major,
203                    minor: Some(self.last_supported_solc_version.minor),
204                    patch: Some(self.last_supported_solc_version.patch),
205                    pre: Prerelease::default(),
206                },
207            ],
208        };
209        if version_req.matches(solc_version) && solc_version >= &MIN_VERSION {
210            Ok(())
211        } else {
212            Err(Error::SolcVersionNotSupported {
213                solc_version: solc_version.clone(),
214                resolc_version: self.version.clone(),
215                supported_range: version_req,
216            })
217        }
218    }
219
220    /// Downloads the binary for the given version
221    pub fn download_binary(&self) -> Result<Vec<u8>, Error> {
222        let binary = reqwest::blocking::ClientBuilder::new()
223            .timeout(Duration::from_secs(300))
224            .build()?
225            .get(self.url.as_ref())
226            .send()?
227            .error_for_status()?;
228        let binary = binary.bytes()?;
229        self.verify_binary(binary.as_ref())?;
230
231        Ok(binary.to_vec())
232    }
233
234    pub(crate) fn into_local(self, path: &Path) -> Binary {
235        Binary::Local {
236            path: path.join(self.version.to_string()).join(self.name),
237            info: BinaryInfo {
238                version: self.version,
239                first_supported_solc_version: self.first_supported_solc_version,
240                last_supported_solc_version: self.last_supported_solc_version,
241            },
242        }
243    }
244
245    pub(crate) fn into_remote(self) -> Binary {
246        Binary::Remote(BinaryInfo {
247            version: self.version,
248            first_supported_solc_version: self.first_supported_solc_version,
249            last_supported_solc_version: self.last_supported_solc_version,
250        })
251    }
252}
253
254#[cfg(test)]
255mod test {
256    use semver::Version;
257
258    use super::{Build, Releases};
259
260    fn release() -> &'static str {
261        r#"{
262            "builds": [
263                {
264                    "name": "resolc-x86_64-unknown-linux-musl",
265                    "version": "0.1.0-dev.13",
266                    "build": "commit.ad331534",
267                    "longVersion": "0.1.0-dev.13+commit.ad331534",
268                    "url": "https://github.com/paritytech/revive/releases/download/v0.1.0-dev.13/resolc-x86_64-unknown-linux-musl",
269                    "sha256": "14d7c165eae626dbe40d182d7f2a435015efb50b1183bf22b0411749106b8c47",
270                    "firstSolcVersion": "0.8.0",
271                    "lastSolcVersion": "0.8.29"
272                }
273            ],
274            "releases": {
275                "0.1.0-dev.13": "resolc-x86_64-unknown-linux-musl+0.1.0-dev.13+commit.ad331534"
276            },
277            "latestRelease": "0.1.0-dev.13"
278        }"#
279    }
280
281    #[test]
282    fn find_version() {
283        let release: Releases = serde_json::from_str(release()).unwrap();
284        release
285            .get_build(&Version::parse("0.1.0-dev.13").unwrap())
286            .unwrap()
287            .check_solc_compat(&Version::new(0, 8, 0))
288            .unwrap()
289    }
290
291    #[test]
292    fn solc_version_support() {
293        let build = r#"
294        {
295            "name": "resolc-x86_64-unknown-linux-musl",
296            "version": "0.1.0-dev.13",
297            "build": "commit.ad331534",
298            "longVersion": "0.1.0-dev.13+commit.ad331534",
299            "url": "https://github.com/paritytech/revive/releases/download/v0.1.0-dev.13/resolc-x86_64-unknown-linux-musl",
300            "sha256": "14d7c165eae626dbe40d182d7f2a435015efb50b1183bf22b0411749106b8c47",
301            "firstSolcVersion": "0.8.0",
302            "lastSolcVersion": "0.8.29"
303        }
304        "#;
305
306        let build: Build = serde_json::from_str(build).unwrap();
307
308        assert_eq!(
309            r#"
310            Unsupported version of `solc` - v0.3.4 for Resolc v0.1.0-dev.13. Only versions ">=0.8.0, <=0.8.29" is supported by this version of Resolc
311            "#.trim(),
312            build
313                .check_solc_compat(&Version::new(0, 3, 4))
314                .expect_err("Expecting error")
315                .to_string()
316        );
317    }
318}