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