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#[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 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 self.builds.extend_from_slice(&other.builds);
32 self.builds.dedup_by_key(|i| i.long_version.clone());
33
34 self.releases.append(&mut other.releases);
36
37 }
39
40 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)]
52pub struct BinaryInfo {
54 pub version: Version,
56 pub first_supported_solc_version: Version,
58 pub last_supported_solc_version: Version,
60}
61
62#[derive(PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
63pub enum Binary {
65 Local {
67 path: PathBuf,
69 info: BinaryInfo,
71 },
72 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 pub fn version(&self) -> &Version {
139 match self {
140 Binary::Local { info, .. } => &info.version,
141 Binary::Remote(info) => &info.version,
142 }
143 }
144 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#[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 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 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}