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
33static LINUX_AARCH64_URL_PREFIX: &str = "https://raw.githubusercontent.com/nikitastupin/solc/2287d4326237172acf91ce42fd7ec18a67b7f512/linux/aarch64";
34
35static LINUX_AARCH64_RELEASES_URL: &str = "https://raw.githubusercontent.com/nikitastupin/solc/2287d4326237172acf91ce42fd7ec18a67b7f512/linux/aarch64/list.json";
36
37const MACOS_AARCH64_NATIVE: Version = Version::new(0, 8, 5);
39
40const UNIVERSAL_MACOS_BINARIES: Version = Version::new(0, 8, 24);
41
42static MACOS_AARCH64_URL_PREFIX: &str = "https://raw.githubusercontent.com/alloy-rs/solc-builds/e4b80d33bc4d015b2fc3583e217fbf248b2014e1/macosx/aarch64";
43
44static MACOS_AARCH64_RELEASES_URL: &str = "https://raw.githubusercontent.com/alloy-rs/solc-builds/e4b80d33bc4d015b2fc3583e217fbf248b2014e1/macosx/aarch64/list.json";
45
46const ANDROID_AARCH64_MIN: Version = Version::new(0, 8, 24);
47
48static ANDROID_AARCH64_URL_PREFIX: &str = "https://raw.githubusercontent.com/alloy-rs/solc-builds/ac6f303a04b38e7ec507ced511fd3ed7a605179f/android/aarch64";
49
50static ANDROID_AARCH64_RELEASES_URL: &str = "https://raw.githubusercontent.com/alloy-rs/solc-builds/ac6f303a04b38e7ec507ced511fd3ed7a605179f/android/aarch64/list.json";
51
52#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
72pub struct Releases {
73 pub builds: Vec<BuildInfo>,
74 pub releases: BTreeMap<Version, String>,
75}
76
77impl Releases {
78 pub fn get_checksum(&self, v: &Version) -> Option<Vec<u8>> {
80 for build in self.builds.iter() {
81 if build.version.eq(v) {
82 return Some(build.sha256.clone());
83 }
84 }
85 None
86 }
87
88 pub fn get_artifact(&self, version: &Version) -> Option<&String> {
90 self.releases.get(version)
91 }
92
93 pub fn into_versions(self) -> Vec<Version> {
95 let mut versions = self.releases.into_keys().collect::<Vec<_>>();
96 versions.sort_unstable();
97 versions
98 }
99}
100
101#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
103pub struct BuildInfo {
104 pub version: Version,
105 #[serde(with = "hex_string")]
106 pub sha256: Vec<u8>,
107}
108
109mod hex_string {
111 use super::*;
112 use serde::{Deserializer, Serializer, de};
113
114 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
115 where
116 D: Deserializer<'de>,
117 {
118 hex::decode(String::deserialize(deserializer)?).map_err(de::Error::custom)
119 }
120
121 pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
122 where
123 S: Serializer,
124 T: AsRef<[u8]>,
125 {
126 serializer.serialize_str(&hex::encode_prefixed(value))
127 }
128}
129
130#[cfg(feature = "blocking")]
132pub fn blocking_all_releases(platform: Platform) -> Result<Releases, SvmError> {
133 match platform {
134 Platform::LinuxAarch64 => {
135 Ok(reqwest::blocking::get(LINUX_AARCH64_RELEASES_URL)?.json::<Releases>()?)
136 }
137 Platform::MacOsAarch64 => {
138 let mut native =
148 reqwest::blocking::get(MACOS_AARCH64_RELEASES_URL)?.json::<Releases>()?;
149 let mut releases = reqwest::blocking::get(format!(
150 "{}/{}/list.json",
151 SOLC_RELEASES_URL,
152 Platform::MacOsAmd64,
153 ))?
154 .json::<Releases>()?;
155 releases.builds.retain(|b| {
156 b.version < MACOS_AARCH64_NATIVE || b.version > UNIVERSAL_MACOS_BINARIES
157 });
158 releases
159 .releases
160 .retain(|v, _| *v < MACOS_AARCH64_NATIVE || *v > UNIVERSAL_MACOS_BINARIES);
161 releases.builds.extend_from_slice(&native.builds);
162
163 releases.releases.append(&mut native.releases);
164 Ok(releases)
165 }
166 Platform::AndroidAarch64 => {
167 Ok(reqwest::blocking::get(ANDROID_AARCH64_RELEASES_URL)?.json::<Releases>()?)
168 }
169 Platform::WindowsAarch64 => {
170 let releases = reqwest::blocking::get(format!(
173 "{SOLC_RELEASES_URL}/{}/list.json",
174 Platform::WindowsAmd64
175 ))?
176 .json::<Releases>()?;
177 Ok(unified_releases(releases, platform))
178 }
179 _ => {
180 let releases =
181 reqwest::blocking::get(format!("{SOLC_RELEASES_URL}/{platform}/list.json"))?
182 .json::<Releases>()?;
183 Ok(unified_releases(releases, platform))
184 }
185 }
186}
187
188pub async fn all_releases(platform: Platform) -> Result<Releases, SvmError> {
190 match platform {
191 Platform::LinuxAarch64 => Ok(get(LINUX_AARCH64_RELEASES_URL)
192 .await?
193 .json::<Releases>()
194 .await?),
195 Platform::MacOsAarch64 => {
196 let mut native = get(MACOS_AARCH64_RELEASES_URL)
204 .await?
205 .json::<Releases>()
206 .await?;
207 let mut releases = get(format!(
208 "{}/{}/list.json",
209 SOLC_RELEASES_URL,
210 Platform::MacOsAmd64,
211 ))
212 .await?
213 .json::<Releases>()
214 .await?;
215 releases.builds.retain(|b| {
216 b.version < MACOS_AARCH64_NATIVE || b.version > UNIVERSAL_MACOS_BINARIES
217 });
218 releases
219 .releases
220 .retain(|v, _| *v < MACOS_AARCH64_NATIVE || *v > UNIVERSAL_MACOS_BINARIES);
221
222 releases.builds.extend_from_slice(&native.builds);
223 releases.releases.append(&mut native.releases);
224 Ok(releases)
225 }
226 Platform::AndroidAarch64 => Ok(get(ANDROID_AARCH64_RELEASES_URL)
227 .await?
228 .json::<Releases>()
229 .await?),
230 Platform::WindowsAarch64 => {
231 let releases = get(format!(
234 "{SOLC_RELEASES_URL}/{}/list.json",
235 Platform::WindowsAmd64
236 ))
237 .await?
238 .json::<Releases>()
239 .await?;
240
241 Ok(unified_releases(releases, platform))
242 }
243 _ => {
244 let releases = get(format!("{SOLC_RELEASES_URL}/{platform}/list.json"))
245 .await?
246 .json::<Releases>()
247 .await?;
248
249 Ok(unified_releases(releases, platform))
250 }
251 }
252}
253
254fn unified_releases(releases: Releases, platform: Platform) -> Releases {
256 if platform == Platform::LinuxAmd64 {
257 let mut all_releases = OLD_SOLC_RELEASES.clone();
258 all_releases.builds.extend(releases.builds);
259 all_releases.releases.extend(releases.releases);
260 all_releases
261 } else {
262 releases
263 }
264}
265
266pub(crate) fn artifact_url(
268 platform: Platform,
269 version: &Version,
270 artifact: &str,
271) -> Result<Url, SvmError> {
272 if platform == Platform::LinuxAmd64
273 && *version <= OLD_VERSION_MAX
274 && *version >= OLD_VERSION_MIN
275 {
276 return Ok(Url::parse(&format!(
277 "{OLD_SOLC_RELEASES_DOWNLOAD_PREFIX}/{artifact}"
278 ))?);
279 }
280
281 if platform == Platform::LinuxAarch64 {
282 if *version >= LINUX_AARCH64_MIN {
283 return Ok(Url::parse(&format!(
284 "{LINUX_AARCH64_URL_PREFIX}/{artifact}"
285 ))?);
286 } else {
287 return Err(SvmError::UnsupportedVersion(
288 version.to_string(),
289 platform.to_string(),
290 ));
291 }
292 }
293
294 if platform == Platform::MacOsAmd64 && *version < OLD_VERSION_MIN {
295 return Err(SvmError::UnsupportedVersion(
296 version.to_string(),
297 platform.to_string(),
298 ));
299 }
300
301 if platform == Platform::MacOsAarch64 {
302 if *version >= MACOS_AARCH64_NATIVE && *version <= UNIVERSAL_MACOS_BINARIES {
303 return Ok(Url::parse(&format!(
305 "{MACOS_AARCH64_URL_PREFIX}/{artifact}"
306 ))?);
307 } else {
308 return Ok(Url::parse(&format!(
310 "{}/{}/{}",
311 SOLC_RELEASES_URL,
312 Platform::MacOsAmd64,
313 artifact,
314 ))?);
315 }
316 }
317
318 if platform == Platform::AndroidAarch64 {
319 if version.ge(&ANDROID_AARCH64_MIN) {
320 return Ok(Url::parse(&format!(
321 "{ANDROID_AARCH64_URL_PREFIX}/{artifact}"
322 ))?);
323 } else {
324 return Err(SvmError::UnsupportedVersion(
325 version.to_string(),
326 platform.to_string(),
327 ));
328 }
329 }
330
331 if platform == Platform::WindowsAarch64 {
332 return Ok(Url::parse(&format!(
335 "{SOLC_RELEASES_URL}/{}/{artifact}",
336 Platform::WindowsAmd64,
337 ))?);
338 }
339
340 Ok(Url::parse(&format!(
341 "{SOLC_RELEASES_URL}/{platform}/{artifact}"
342 ))?)
343}
344
345#[cfg(test)]
346mod tests {
347 use super::*;
348
349 #[test]
350 fn test_artifact_url() {
351 let version = Version::new(0, 5, 0);
352 let artifact = "solc-v0.5.0";
353 assert_eq!(
354 artifact_url(Platform::LinuxAarch64, &version, artifact).unwrap(),
355 Url::parse(&format!(
356 "https://raw.githubusercontent.com/nikitastupin/solc/2287d4326237172acf91ce42fd7ec18a67b7f512/linux/aarch64/{artifact}"
357 ))
358 .unwrap(),
359 )
360 }
361
362 #[test]
363 fn test_old_releases_deser() {
364 assert_eq!(OLD_SOLC_RELEASES.releases.len(), 10);
365 assert_eq!(OLD_SOLC_RELEASES.builds.len(), 10);
366 }
367
368 #[tokio::test]
369 async fn test_macos_aarch64() {
370 let releases = all_releases(Platform::MacOsAarch64)
371 .await
372 .expect("could not fetch releases for macos-aarch64");
373 let rosetta = Version::new(0, 8, 4);
374 let native = MACOS_AARCH64_NATIVE;
375 let url1 = artifact_url(
376 Platform::MacOsAarch64,
377 &rosetta,
378 releases.get_artifact(&rosetta).unwrap(),
379 )
380 .expect("could not fetch artifact URL");
381 let url2 = artifact_url(
382 Platform::MacOsAarch64,
383 &native,
384 releases.get_artifact(&native).unwrap(),
385 )
386 .expect("could not fetch artifact URL");
387 assert!(url1.to_string().contains(SOLC_RELEASES_URL));
388 assert!(url2.to_string().contains(MACOS_AARCH64_URL_PREFIX));
389 }
390
391 #[tokio::test]
392 async fn test_all_releases_macos_amd64() {
393 assert!(all_releases(Platform::MacOsAmd64).await.is_ok());
394 }
395
396 #[tokio::test]
397 async fn test_all_releases_macos_aarch64() {
398 assert!(all_releases(Platform::MacOsAarch64).await.is_ok());
399 }
400
401 #[tokio::test]
402 async fn test_all_releases_linux_amd64() {
403 assert!(all_releases(Platform::LinuxAmd64).await.is_ok());
404 }
405
406 #[tokio::test]
407 async fn test_all_releases_linux_aarch64() {
408 assert!(all_releases(Platform::LinuxAarch64).await.is_ok());
409 }
410
411 #[tokio::test]
412 async fn test_all_releases_windows_aarch64() {
413 let releases = all_releases(Platform::WindowsAarch64).await;
414 assert!(releases.is_ok());
415 let releases = releases.unwrap();
417 let latest = releases.releases.keys().max().unwrap();
418 let artifact = releases.get_artifact(latest).unwrap();
419 let url = artifact_url(Platform::WindowsAarch64, latest, artifact).unwrap();
420 assert!(url.to_string().contains("windows-amd64"));
421 }
422
423 #[tokio::test]
424 async fn releases_roundtrip() {
425 let releases = all_releases(Platform::LinuxAmd64).await.unwrap();
426 let s = serde_json::to_string(&releases).unwrap();
427 let de_releases: Releases = serde_json::from_str(&s).unwrap();
428 assert_eq!(releases, de_releases);
429 }
430}