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