1use crate::{error::SvmError, platform::Platform};
2use reqwest::get;
3use semver::Version;
4use serde::{Deserialize, Serialize};
5use std::{collections::BTreeMap, sync::LazyLock};
6use url::Url;
7
8const SOLC_RELEASES_URL: &str = "https://binaries.soliditylang.org";
18
19const OLD_SOLC_RELEASES_DOWNLOAD_PREFIX: &str =
20 "https://raw.githubusercontent.com/crytic/solc/master/linux/amd64";
21
22const OLD_VERSION_MAX: Version = Version::new(0, 4, 9);
23
24const OLD_VERSION_MIN: Version = Version::new(0, 4, 0);
25
26static OLD_SOLC_RELEASES: LazyLock<Releases> = LazyLock::new(|| {
27 serde_json::from_str(include_str!("../list/linux-arm64-old.json"))
28 .expect("could not parse list linux-arm64-old.json")
29});
30
31const LINUX_AARCH64_MIN: Version = Version::new(0, 5, 0);
32
33const LINUX_AARCH64_BINARIES: Version = Version::new(0, 8, 31);
35
36static LINUX_AARCH64_PLATFORM: &str = "linux-arm64";
39
40static LINUX_AARCH64_URL_PREFIX: &str = "https://raw.githubusercontent.com/nikitastupin/solc/2287d4326237172acf91ce42fd7ec18a67b7f512/linux/aarch64";
41
42static LINUX_AARCH64_RELEASES_URL: &str = "https://raw.githubusercontent.com/nikitastupin/solc/2287d4326237172acf91ce42fd7ec18a67b7f512/linux/aarch64/list.json";
43
44const MACOS_AARCH64_NATIVE: Version = Version::new(0, 8, 5);
46
47const UNIVERSAL_MACOS_BINARIES: Version = Version::new(0, 8, 24);
48
49static MACOS_AARCH64_URL_PREFIX: &str = "https://raw.githubusercontent.com/alloy-rs/solc-builds/e4b80d33bc4d015b2fc3583e217fbf248b2014e1/macosx/aarch64";
50
51static MACOS_AARCH64_RELEASES_URL: &str = "https://raw.githubusercontent.com/alloy-rs/solc-builds/e4b80d33bc4d015b2fc3583e217fbf248b2014e1/macosx/aarch64/list.json";
52
53const ANDROID_AARCH64_MIN: Version = Version::new(0, 8, 24);
54
55static ANDROID_AARCH64_URL_PREFIX: &str = "https://raw.githubusercontent.com/alloy-rs/solc-builds/ac6f303a04b38e7ec507ced511fd3ed7a605179f/android/aarch64";
56
57static ANDROID_AARCH64_RELEASES_URL: &str = "https://raw.githubusercontent.com/alloy-rs/solc-builds/ac6f303a04b38e7ec507ced511fd3ed7a605179f/android/aarch64/list.json";
58
59#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
79pub struct Releases {
80 pub builds: Vec<BuildInfo>,
81 pub releases: BTreeMap<Version, String>,
82}
83
84impl Releases {
85 pub fn get_checksum(&self, v: &Version) -> Option<Vec<u8>> {
88 let matches = |build_info: &BuildInfo| {
89 build_info.version == Version::new(v.major, v.minor, v.patch)
90 && build_info
91 .prerelease
92 .as_deref()
93 .unwrap_or("")
94 .eq_ignore_ascii_case(v.pre.as_str())
95 };
96
97 self.builds
98 .iter()
99 .find(|build_info| matches(build_info))
100 .map(|build_info| build_info.sha256.clone())
101 }
102
103 pub fn get_artifact(&self, version: &Version) -> Option<&String> {
106 if let Some(artifact) = self.releases.get(version) {
108 return Some(artifact);
109 }
110
111 if !version.pre.is_empty()
113 && let Some(build_info) = self.builds.iter().find(|b| {
114 b.version == Version::new(version.major, version.minor, version.patch)
115 && b.prerelease == Some(version.pre.to_string())
116 })
117 {
118 return build_info.path.as_ref();
119 }
120
121 None
122 }
123
124 pub fn into_versions(self) -> Vec<Version> {
126 let mut versions = self.releases.into_keys().collect::<Vec<_>>();
127 versions.sort_unstable();
128 versions
129 }
130
131 fn retain_versions(&mut self, mut pred: impl FnMut(&Version) -> bool) {
133 self.builds.retain(|build| pred(&build.version));
134 self.releases.retain(|version, _| pred(version));
135 }
136
137 fn extend(&mut self, other: Self) {
139 self.builds.extend(other.builds);
140 self.releases.extend(other.releases);
141 }
142}
143
144#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
146pub struct BuildInfo {
147 pub version: Version,
148 #[serde(with = "hex_string")]
149 pub sha256: Vec<u8>,
150 pub path: Option<String>,
151 pub prerelease: Option<String>,
152}
153
154mod hex_string {
156 use super::*;
157 use serde::{Deserializer, Serializer, de};
158
159 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
160 where
161 D: Deserializer<'de>,
162 {
163 hex::decode(String::deserialize(deserializer)?).map_err(de::Error::custom)
164 }
165
166 pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
167 where
168 S: Serializer,
169 T: AsRef<[u8]>,
170 {
171 serializer.serialize_str(&hex::encode_prefixed(value))
172 }
173}
174
175#[cfg(feature = "blocking")]
177pub fn blocking_all_releases(platform: Platform) -> Result<Releases, SvmError> {
178 match platform {
179 Platform::LinuxAarch64 => {
180 let mut releases =
184 reqwest::blocking::get(LINUX_AARCH64_RELEASES_URL)?.json::<Releases>()?;
185 releases.retain_versions(|v| *v < LINUX_AARCH64_BINARIES);
186 fix_build_prerelease(&mut releases);
187 let mut official = reqwest::blocking::get(format!(
188 "{SOLC_RELEASES_URL}/{LINUX_AARCH64_PLATFORM}/list.json",
189 ))?
190 .json::<Releases>()?;
191 official.retain_versions(|v| *v >= LINUX_AARCH64_BINARIES);
194 releases.extend(official);
195 Ok(releases)
196 }
197 Platform::MacOsAarch64 => {
198 let native = reqwest::blocking::get(MACOS_AARCH64_RELEASES_URL)?.json::<Releases>()?;
208 let mut releases = reqwest::blocking::get(format!(
209 "{}/{}/list.json",
210 SOLC_RELEASES_URL,
211 Platform::MacOsAmd64,
212 ))?
213 .json::<Releases>()?;
214 releases
215 .retain_versions(|v| *v < MACOS_AARCH64_NATIVE || *v > UNIVERSAL_MACOS_BINARIES);
216 releases.extend(native);
217 Ok(releases)
218 }
219 Platform::AndroidAarch64 => {
220 Ok(reqwest::blocking::get(ANDROID_AARCH64_RELEASES_URL)?.json::<Releases>()?)
221 }
222 Platform::WindowsAarch64 => {
223 let releases = reqwest::blocking::get(format!(
226 "{SOLC_RELEASES_URL}/{}/list.json",
227 Platform::WindowsAmd64
228 ))?
229 .json::<Releases>()?;
230 Ok(unified_releases(releases, platform))
231 }
232 _ => {
233 let releases =
234 reqwest::blocking::get(format!("{SOLC_RELEASES_URL}/{platform}/list.json"))?
235 .json::<Releases>()?;
236 Ok(unified_releases(releases, platform))
237 }
238 }
239}
240
241pub async fn all_releases(platform: Platform) -> Result<Releases, SvmError> {
243 match platform {
244 Platform::LinuxAarch64 => {
245 let mut releases = get(LINUX_AARCH64_RELEASES_URL)
249 .await?
250 .json::<Releases>()
251 .await?;
252 releases.retain_versions(|v| *v < LINUX_AARCH64_BINARIES);
253 fix_build_prerelease(&mut releases);
254 let mut official = get(format!(
255 "{SOLC_RELEASES_URL}/{LINUX_AARCH64_PLATFORM}/list.json",
256 ))
257 .await?
258 .json::<Releases>()
259 .await?;
260 official.retain_versions(|v| *v >= LINUX_AARCH64_BINARIES);
263 releases.extend(official);
264 Ok(releases)
265 }
266 Platform::MacOsAarch64 => {
267 let native = get(MACOS_AARCH64_RELEASES_URL)
275 .await?
276 .json::<Releases>()
277 .await?;
278 let mut releases = get(format!(
279 "{}/{}/list.json",
280 SOLC_RELEASES_URL,
281 Platform::MacOsAmd64,
282 ))
283 .await?
284 .json::<Releases>()
285 .await?;
286 releases
287 .retain_versions(|v| *v < MACOS_AARCH64_NATIVE || *v > UNIVERSAL_MACOS_BINARIES);
288
289 releases.extend(native);
290 Ok(releases)
291 }
292 Platform::AndroidAarch64 => Ok(get(ANDROID_AARCH64_RELEASES_URL)
293 .await?
294 .json::<Releases>()
295 .await?),
296 Platform::WindowsAarch64 => {
297 let releases = get(format!(
300 "{SOLC_RELEASES_URL}/{}/list.json",
301 Platform::WindowsAmd64
302 ))
303 .await?
304 .json::<Releases>()
305 .await?;
306
307 Ok(unified_releases(releases, platform))
308 }
309 _ => {
310 let releases = get(format!("{SOLC_RELEASES_URL}/{platform}/list.json"))
311 .await?
312 .json::<Releases>()
313 .await?;
314
315 Ok(unified_releases(releases, platform))
316 }
317 }
318}
319
320fn unified_releases(releases: Releases, platform: Platform) -> Releases {
322 if platform == Platform::LinuxAmd64 {
323 let mut all_releases = OLD_SOLC_RELEASES.clone();
324 all_releases.extend(releases);
325 all_releases
326 } else {
327 releases
328 }
329}
330
331fn fix_build_prerelease(releases: &mut Releases) {
336 for build in &mut releases.builds {
337 build.prerelease = if build.version.pre.is_empty() {
338 None
339 } else {
340 Some(build.version.pre.as_str().to_owned())
341 };
342 }
343}
344
345pub(crate) fn artifact_url(
347 platform: Platform,
348 version: &Version,
349 artifact: &str,
350) -> Result<Url, SvmError> {
351 if platform == Platform::LinuxAmd64
352 && *version <= OLD_VERSION_MAX
353 && *version >= OLD_VERSION_MIN
354 {
355 return Ok(Url::parse(&format!(
356 "{OLD_SOLC_RELEASES_DOWNLOAD_PREFIX}/{artifact}"
357 ))?);
358 }
359
360 if platform == Platform::LinuxAarch64 {
361 if *version >= LINUX_AARCH64_BINARIES {
362 return Ok(Url::parse(&format!(
363 "{SOLC_RELEASES_URL}/{LINUX_AARCH64_PLATFORM}/{artifact}"
364 ))?);
365 } else if *version >= LINUX_AARCH64_MIN {
366 return Ok(Url::parse(&format!(
367 "{LINUX_AARCH64_URL_PREFIX}/{artifact}"
368 ))?);
369 } else {
370 return Err(SvmError::UnsupportedVersion(
371 version.to_string(),
372 platform.to_string(),
373 ));
374 }
375 }
376
377 if platform == Platform::MacOsAmd64 && *version < OLD_VERSION_MIN {
378 return Err(SvmError::UnsupportedVersion(
379 version.to_string(),
380 platform.to_string(),
381 ));
382 }
383
384 if platform == Platform::MacOsAarch64 {
385 if *version >= MACOS_AARCH64_NATIVE && *version <= UNIVERSAL_MACOS_BINARIES {
386 return Ok(Url::parse(&format!(
388 "{MACOS_AARCH64_URL_PREFIX}/{artifact}"
389 ))?);
390 } else {
391 return Ok(Url::parse(&format!(
393 "{}/{}/{}",
394 SOLC_RELEASES_URL,
395 Platform::MacOsAmd64,
396 artifact,
397 ))?);
398 }
399 }
400
401 if platform == Platform::AndroidAarch64 {
402 if version.ge(&ANDROID_AARCH64_MIN) {
403 return Ok(Url::parse(&format!(
404 "{ANDROID_AARCH64_URL_PREFIX}/{artifact}"
405 ))?);
406 } else {
407 return Err(SvmError::UnsupportedVersion(
408 version.to_string(),
409 platform.to_string(),
410 ));
411 }
412 }
413
414 if platform == Platform::WindowsAarch64 {
415 return Ok(Url::parse(&format!(
418 "{SOLC_RELEASES_URL}/{}/{artifact}",
419 Platform::WindowsAmd64,
420 ))?);
421 }
422
423 Ok(Url::parse(&format!(
424 "{SOLC_RELEASES_URL}/{platform}/{artifact}"
425 ))?)
426}
427
428#[cfg(test)]
429mod tests {
430 use super::*;
431
432 #[test]
433 fn test_artifact_url() {
434 let version = Version::new(0, 5, 0);
435 let artifact = "solc-v0.5.0";
436 assert_eq!(
437 artifact_url(Platform::LinuxAarch64, &version, artifact).unwrap(),
438 Url::parse(&format!(
439 "https://raw.githubusercontent.com/nikitastupin/solc/2287d4326237172acf91ce42fd7ec18a67b7f512/linux/aarch64/{artifact}"
440 ))
441 .unwrap(),
442 )
443 }
444
445 #[test]
446 fn test_old_releases_deser() {
447 assert_eq!(OLD_SOLC_RELEASES.releases.len(), 10);
448 assert_eq!(OLD_SOLC_RELEASES.builds.len(), 10);
449 }
450
451 #[tokio::test]
452 async fn test_macos_aarch64() {
453 let releases = all_releases(Platform::MacOsAarch64)
454 .await
455 .expect("could not fetch releases for macos-aarch64");
456 let rosetta = Version::new(0, 8, 4);
457 let native = MACOS_AARCH64_NATIVE;
458 let url1 = artifact_url(
459 Platform::MacOsAarch64,
460 &rosetta,
461 releases.get_artifact(&rosetta).unwrap(),
462 )
463 .expect("could not fetch artifact URL");
464 let url2 = artifact_url(
465 Platform::MacOsAarch64,
466 &native,
467 releases.get_artifact(&native).unwrap(),
468 )
469 .expect("could not fetch artifact URL");
470 assert!(url1.to_string().contains(SOLC_RELEASES_URL));
471 assert!(url2.to_string().contains(MACOS_AARCH64_URL_PREFIX));
472 }
473
474 #[tokio::test]
475 async fn test_all_releases_macos_amd64() {
476 assert!(all_releases(Platform::MacOsAmd64).await.is_ok());
477 }
478
479 #[tokio::test]
480 async fn test_all_releases_macos_aarch64() {
481 assert!(all_releases(Platform::MacOsAarch64).await.is_ok());
482 }
483
484 #[tokio::test]
485 async fn test_all_releases_linux_amd64() {
486 assert!(all_releases(Platform::LinuxAmd64).await.is_ok());
487 }
488
489 #[tokio::test]
490 async fn test_all_releases_linux_aarch64() {
491 let releases = all_releases(Platform::LinuxAarch64)
492 .await
493 .expect("could not fetch releases for linux-aarch64");
494 let thirdparty = LINUX_AARCH64_MIN;
495 let url1 = artifact_url(
496 Platform::LinuxAarch64,
497 &thirdparty,
498 releases.get_artifact(&thirdparty).unwrap(),
499 )
500 .expect("could not fetch artifact URL");
501 let prerelease = Version::parse("0.8.31-pre.1").expect("failed to parse version");
502 let url2 = artifact_url(
503 Platform::LinuxAarch64,
504 &prerelease,
505 releases.get_artifact(&prerelease).unwrap(),
506 )
507 .expect("could not fetch artifact URL");
508 let official = LINUX_AARCH64_BINARIES;
509 let url3 = artifact_url(
510 Platform::LinuxAarch64,
511 &official,
512 releases.get_artifact(&official).unwrap(),
513 )
514 .expect("could not fetch artifact URL");
515 assert!(url1.to_string().contains(LINUX_AARCH64_URL_PREFIX));
516 assert!(url2.to_string().contains(LINUX_AARCH64_URL_PREFIX));
517 assert!(url3.to_string().contains(SOLC_RELEASES_URL));
518 }
519
520 #[tokio::test]
521 async fn test_all_releases_windows_aarch64() {
522 let releases = all_releases(Platform::WindowsAarch64).await;
523 assert!(releases.is_ok());
524 let releases = releases.unwrap();
526 let latest = releases.releases.keys().max().unwrap();
527 let artifact = releases.get_artifact(latest).unwrap();
528 let url = artifact_url(Platform::WindowsAarch64, latest, artifact).unwrap();
529 assert!(url.to_string().contains("windows-amd64"));
530 }
531
532 #[tokio::test]
533 async fn releases_roundtrip() {
534 let releases = all_releases(Platform::LinuxAmd64).await.unwrap();
535 let s = serde_json::to_string(&releases).unwrap();
536 let de_releases: Releases = serde_json::from_str(&s).unwrap();
537 assert_eq!(releases, de_releases);
538 }
539}