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 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)]
41pub struct BinaryInfo {
43 pub version: Version,
45 pub first_supported_solc_version: Version,
47 pub last_supported_solc_version: Version,
49}
50
51#[derive(PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
52pub enum Binary {
54 Local {
56 path: PathBuf,
58 info: BinaryInfo,
60 },
61 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 pub fn version(&self) -> &Version {
128 match self {
129 Binary::Local { info, .. } => &info.version,
130 Binary::Remote(info) => &info.version,
131 }
132 }
133 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#[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 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 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}