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/0045084c85d8c159de03442a37d2018d52374445/linux/aarch64";
34
35static LINUX_AARCH64_RELEASES_URL: &str = "https://raw.githubusercontent.com/nikitastupin/solc/0045084c85d8c159de03442a37d2018d52374445/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 _ => {
170 let releases =
171 reqwest::blocking::get(format!("{SOLC_RELEASES_URL}/{platform}/list.json"))?
172 .json::<Releases>()?;
173 Ok(unified_releases(releases, platform))
174 }
175 }
176}
177
178pub async fn all_releases(platform: Platform) -> Result<Releases, SvmError> {
180 match platform {
181 Platform::LinuxAarch64 => Ok(get(LINUX_AARCH64_RELEASES_URL)
182 .await?
183 .json::<Releases>()
184 .await?),
185 Platform::MacOsAarch64 => {
186 let mut native = get(MACOS_AARCH64_RELEASES_URL)
194 .await?
195 .json::<Releases>()
196 .await?;
197 let mut releases = get(format!(
198 "{}/{}/list.json",
199 SOLC_RELEASES_URL,
200 Platform::MacOsAmd64,
201 ))
202 .await?
203 .json::<Releases>()
204 .await?;
205 releases.builds.retain(|b| {
206 b.version < MACOS_AARCH64_NATIVE || b.version > UNIVERSAL_MACOS_BINARIES
207 });
208 releases
209 .releases
210 .retain(|v, _| *v < MACOS_AARCH64_NATIVE || *v > UNIVERSAL_MACOS_BINARIES);
211
212 releases.builds.extend_from_slice(&native.builds);
213 releases.releases.append(&mut native.releases);
214 Ok(releases)
215 }
216 Platform::AndroidAarch64 => Ok(get(ANDROID_AARCH64_RELEASES_URL)
217 .await?
218 .json::<Releases>()
219 .await?),
220 _ => {
221 let releases = get(format!("{SOLC_RELEASES_URL}/{platform}/list.json"))
222 .await?
223 .json::<Releases>()
224 .await?;
225
226 Ok(unified_releases(releases, platform))
227 }
228 }
229}
230
231fn unified_releases(releases: Releases, platform: Platform) -> Releases {
233 if platform == Platform::LinuxAmd64 {
234 let mut all_releases = OLD_SOLC_RELEASES.clone();
235 all_releases.builds.extend(releases.builds);
236 all_releases.releases.extend(releases.releases);
237 all_releases
238 } else {
239 releases
240 }
241}
242
243pub(crate) fn artifact_url(
245 platform: Platform,
246 version: &Version,
247 artifact: &str,
248) -> Result<Url, SvmError> {
249 if platform == Platform::LinuxAmd64
250 && *version <= OLD_VERSION_MAX
251 && *version >= OLD_VERSION_MIN
252 {
253 return Ok(Url::parse(&format!(
254 "{OLD_SOLC_RELEASES_DOWNLOAD_PREFIX}/{artifact}"
255 ))?);
256 }
257
258 if platform == Platform::LinuxAarch64 {
259 if *version >= LINUX_AARCH64_MIN {
260 return Ok(Url::parse(&format!(
261 "{LINUX_AARCH64_URL_PREFIX}/{artifact}"
262 ))?);
263 } else {
264 return Err(SvmError::UnsupportedVersion(
265 version.to_string(),
266 platform.to_string(),
267 ));
268 }
269 }
270
271 if platform == Platform::MacOsAmd64 && *version < OLD_VERSION_MIN {
272 return Err(SvmError::UnsupportedVersion(
273 version.to_string(),
274 platform.to_string(),
275 ));
276 }
277
278 if platform == Platform::MacOsAarch64 {
279 if *version >= MACOS_AARCH64_NATIVE && *version <= UNIVERSAL_MACOS_BINARIES {
280 return Ok(Url::parse(&format!(
282 "{MACOS_AARCH64_URL_PREFIX}/{artifact}"
283 ))?);
284 } else {
285 return Ok(Url::parse(&format!(
287 "{}/{}/{}",
288 SOLC_RELEASES_URL,
289 Platform::MacOsAmd64,
290 artifact,
291 ))?);
292 }
293 }
294
295 if platform == Platform::AndroidAarch64 {
296 if version.ge(&ANDROID_AARCH64_MIN) {
297 return Ok(Url::parse(&format!(
298 "{ANDROID_AARCH64_URL_PREFIX}/{artifact}"
299 ))?);
300 } else {
301 return Err(SvmError::UnsupportedVersion(
302 version.to_string(),
303 platform.to_string(),
304 ));
305 }
306 }
307
308 Ok(Url::parse(&format!(
309 "{SOLC_RELEASES_URL}/{platform}/{artifact}"
310 ))?)
311}
312
313#[cfg(test)]
314mod tests {
315 use super::*;
316
317 #[test]
318 fn test_artifact_url() {
319 let version = Version::new(0, 5, 0);
320 let artifact = "solc-v0.5.0";
321 assert_eq!(
322 artifact_url(Platform::LinuxAarch64, &version, artifact).unwrap(),
323 Url::parse(&format!(
324 "https://raw.githubusercontent.com/nikitastupin/solc/0045084c85d8c159de03442a37d2018d52374445/linux/aarch64/{artifact}"
325 ))
326 .unwrap(),
327 )
328 }
329
330 #[test]
331 fn test_old_releases_deser() {
332 assert_eq!(OLD_SOLC_RELEASES.releases.len(), 10);
333 assert_eq!(OLD_SOLC_RELEASES.builds.len(), 10);
334 }
335
336 #[tokio::test]
337 async fn test_macos_aarch64() {
338 let releases = all_releases(Platform::MacOsAarch64)
339 .await
340 .expect("could not fetch releases for macos-aarch64");
341 let rosetta = Version::new(0, 8, 4);
342 let native = MACOS_AARCH64_NATIVE;
343 let url1 = artifact_url(
344 Platform::MacOsAarch64,
345 &rosetta,
346 releases.get_artifact(&rosetta).unwrap(),
347 )
348 .expect("could not fetch artifact URL");
349 let url2 = artifact_url(
350 Platform::MacOsAarch64,
351 &native,
352 releases.get_artifact(&native).unwrap(),
353 )
354 .expect("could not fetch artifact URL");
355 assert!(url1.to_string().contains(SOLC_RELEASES_URL));
356 assert!(url2.to_string().contains(MACOS_AARCH64_URL_PREFIX));
357 }
358
359 #[tokio::test]
360 async fn test_all_releases_macos_amd64() {
361 assert!(all_releases(Platform::MacOsAmd64).await.is_ok());
362 }
363
364 #[tokio::test]
365 async fn test_all_releases_macos_aarch64() {
366 assert!(all_releases(Platform::MacOsAarch64).await.is_ok());
367 }
368
369 #[tokio::test]
370 async fn test_all_releases_linux_amd64() {
371 assert!(all_releases(Platform::LinuxAmd64).await.is_ok());
372 }
373
374 #[tokio::test]
375 async fn test_all_releases_linux_aarch64() {
376 assert!(all_releases(Platform::LinuxAarch64).await.is_ok());
377 }
378
379 #[tokio::test]
380 async fn releases_roundtrip() {
381 let releases = all_releases(Platform::LinuxAmd64).await.unwrap();
382 let s = serde_json::to_string(&releases).unwrap();
383 let de_releases: Releases = serde_json::from_str(&s).unwrap();
384 assert_eq!(releases, de_releases);
385 }
386}