Skip to content

Commit 032a571

Browse files
authored
Merge pull request #240 from crytic/alloy-aarch64
Add support for ARM64 Darwin binaries
2 parents bbb538c + 833daac commit 032a571

File tree

4 files changed

+124
-35
lines changed

4 files changed

+124
-35
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ To automatically install and use a version, run `solc-select use <version> --alw
3030

3131
### Running on ARM (Mac M1/M2)
3232

33-
`solc` older than 0.8.24 requires Rosetta to be installed. See the FAQ on [how to install Rosetta](#oserror-errno-86-bad-cpu-type-in-executable).
33+
`solc-select` provides native ARM64 support for versions 0.8.5-0.8.23, and universal binary support for 0.8.24+. For versions older than 0.8.5, Rosetta is required. See the FAQ on [how to install Rosetta](#oserror-errno-86-bad-cpu-type-in-executable).
3434

3535
## Usage
3636

@@ -107,10 +107,10 @@ Feel free to stop by our [Slack channel](https://empirehacking.slack.com/) for h
107107

108108
On newer `solc-select` versions, this might show as `solc binaries for macOS are
109109
Intel-only. Please install Rosetta on your Mac to continue.` or `solc binaries
110-
previous to 0.8.24 for macOS are Intel-only. Please install Rosetta on your Mac
110+
previous to 0.8.5 for macOS are Intel-only. Please install Rosetta on your Mac
111111
to continue.`
112112

113-
`solc` releases earlier than 0.8.24 require Rosetta to be installed. To see
113+
`solc` releases earlier than 0.8.5 require Rosetta to be installed. Versions 0.8.5-0.8.23 run natively on ARM64, and 0.8.24+ use universal binaries. To see
114114
whether you have Rosetta installed on your Mac, run
115115

116116
```bash

solc_select/constants.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,10 @@
2626
CRYTIC_SOLC_JSON = (
2727
"https://raw.githubusercontent.com/crytic/solc/new-list-json/linux/amd64/list.json"
2828
)
29+
30+
ALLOY_SOLC_ARTIFACTS = "https://raw.githubusercontent.com/alloy-rs/solc-builds/203ef20a24a6c2cb763e1c8c4c1836e85db2512d/macosx/aarch64/"
31+
ALLOY_SOLC_JSON = "https://raw.githubusercontent.com/alloy-rs/solc-builds/203ef20a24a6c2cb763e1c8c4c1836e85db2512d/macosx/aarch64/list.json"
32+
33+
# Alloy ARM64 version range (0.8.24+ are universal binaries)
34+
ALLOY_ARM64_MIN_VERSION = "0.8.5"
35+
ALLOY_ARM64_MAX_VERSION = "0.8.23"

solc_select/solc_select.py

Lines changed: 91 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,23 @@
33
import hashlib
44
import json
55
import os
6-
import platform
76
import re
87
import shutil
98
import subprocess
109
import sys
1110
import urllib.request
1211
from pathlib import Path
13-
from typing import Dict, List, Tuple
12+
from typing import Dict, List, Optional, Tuple
1413
from zipfile import ZipFile
1514

1615
from Crypto.Hash import keccak
1716
from packaging.version import Version
1817

1918
from .constants import (
19+
ALLOY_ARM64_MAX_VERSION,
20+
ALLOY_ARM64_MIN_VERSION,
21+
ALLOY_SOLC_ARTIFACTS,
22+
ALLOY_SOLC_JSON,
2023
ARTIFACTS_DIR,
2124
CRYTIC_SOLC_ARTIFACTS,
2225
CRYTIC_SOLC_JSON,
@@ -26,23 +29,16 @@
2629
SOLC_SELECT_DIR,
2730
WINDOWS_AMD64,
2831
)
29-
from .utils import mac_binary_is_universal, mac_can_run_intel_binaries
32+
from .utils import (
33+
get_arch,
34+
mac_binary_is_native,
35+
mac_binary_is_universal,
36+
mac_can_run_intel_binaries,
37+
)
3038

3139
Path.mkdir(ARTIFACTS_DIR, parents=True, exist_ok=True)
3240

3341

34-
def get_arch() -> str:
35-
"""Get the current system architecture."""
36-
machine = platform.machine().lower()
37-
if machine in ["x86_64", "amd64"]:
38-
return "amd64"
39-
elif machine in ["aarch64", "arm64"]:
40-
return "arm64"
41-
elif machine in ["i386", "i686"]:
42-
return "386"
43-
return machine
44-
45-
4642
def check_emulation_available() -> bool:
4743
"""Check if x86_64 emulation is available."""
4844
if get_arch() != "arm64":
@@ -91,22 +87,34 @@ def warn_about_arm64(force: bool = False) -> None:
9187
print("\n⚠️ WARNING: ARM64 Architecture Detected", file=sys.stderr)
9288
print("=" * 50, file=sys.stderr)
9389

94-
if check_emulation_available():
95-
if sys.platform == "darwin":
96-
print("✓ Rosetta 2 detected - will use emulation for x86 binaries", file=sys.stderr)
90+
show_remediation = False
91+
92+
if sys.platform == "darwin":
93+
print("✓ Native ARM64 binaries available for versions 0.8.5-0.8.23", file=sys.stderr)
94+
print("✓ Universal binaries available for versions 0.8.24+", file=sys.stderr)
95+
if check_emulation_available():
96+
print("✓ Rosetta 2 detected - will use emulation for older versions", file=sys.stderr)
97+
print(" Note: Performance will be slower for emulated versions", file=sys.stderr)
9798
else:
98-
print("✓ qemu-x86_64 detected - will use emulation for x86 binaries", file=sys.stderr)
99-
print(" Note: Performance will be slower than native execution", file=sys.stderr)
100-
else:
101-
if sys.platform == "darwin":
10299
print(
103-
"✗ solc binaries are x86_64 only, and Rosetta 2 is not available", file=sys.stderr
100+
"⚠ Rosetta 2 not available - versions prior to 0.8.5 are x86_64 only and will not work",
101+
file=sys.stderr,
104102
)
103+
show_remediation = True
104+
elif sys.platform == "linux":
105+
if check_emulation_available():
106+
print("✓ qemu-x86_64 detected - will use emulation for x86 binaries", file=sys.stderr)
107+
print(" Note: Performance will be slower than native execution", file=sys.stderr)
105108
else:
106109
print("✗ solc binaries are x86_64 only, and qemu is not installed", file=sys.stderr)
110+
show_remediation = True
111+
else:
112+
show_remediation = True
113+
114+
if show_remediation:
107115
print("\nTo use solc-select on ARM64, you can:", file=sys.stderr)
108-
print(" 1. Install qemu for x86_64 emulation:", file=sys.stderr)
109-
if sys.platform.startswith("linux"):
116+
print(" 1. Install software for x86_64 emulation:", file=sys.stderr)
117+
if sys.platform == "linux":
110118
print(" sudo apt-get install qemu-user-static # Debian/Ubuntu", file=sys.stderr)
111119
print(" sudo dnf install qemu-user-static # Fedora", file=sys.stderr)
112120
print(" sudo pacman -S qemu-user-static # Arch", file=sys.stderr)
@@ -140,8 +148,12 @@ def halt_incompatible_system(path: Path) -> None:
140148
if mac_binary_is_universal(path):
141149
return
142150

151+
# If the binary is native to this architecture, we can run it
152+
if mac_binary_is_native(path):
153+
return
154+
143155
raise argparse.ArgumentTypeError(
144-
"solc binaries previous to 0.8.24 for macOS are Intel-only. Please install Rosetta on your Mac to continue. Refer to the solc-select README for instructions."
156+
"solc binaries previous to 0.8.5 for macOS are Intel-only. Please install Rosetta on your Mac to continue. Refer to the solc-select README for instructions."
145157
)
146158
# TODO: check for Linux aarch64 (e.g. RPi), presence of QEMU+binfmt
147159

@@ -261,6 +273,14 @@ def is_older_windows(version: str) -> bool:
261273
return soliditylang_platform() == WINDOWS_AMD64 and Version(version) <= Version("0.7.1")
262274

263275

276+
def is_alloy_aarch64_version(version: str) -> bool:
277+
return (
278+
sys.platform == "darwin"
279+
and get_arch() == "arm64"
280+
and Version(ALLOY_ARM64_MIN_VERSION) <= Version(version) <= Version(ALLOY_ARM64_MAX_VERSION)
281+
)
282+
283+
264284
def verify_checksum(version: str) -> None:
265285
(sha256_hash, keccak256_hash) = get_soliditylang_checksums(version)
266286

@@ -274,16 +294,21 @@ def verify_checksum(version: str) -> None:
274294
sha256_factory.update(chunk)
275295
keccak_factory.update(chunk)
276296

277-
local_sha256_file_hash = f"0x{sha256_factory.hexdigest()}"
278-
local_keccak256_file_hash = f"0x{keccak_factory.hexdigest()}"
297+
local_sha256_file_hash = sha256_factory.hexdigest()
298+
local_keccak256_file_hash = keccak_factory.hexdigest()
279299

280-
if sha256_hash != local_sha256_file_hash or keccak256_hash != local_keccak256_file_hash:
300+
if sha256_hash != local_sha256_file_hash:
281301
raise argparse.ArgumentTypeError(
282-
f"Error: Checksum mismatch {soliditylang_platform()} - {version}"
302+
f"Error: SHA256 checksum mismatch {soliditylang_platform()} - {version}"
303+
)
304+
305+
if keccak256_hash is not None and keccak256_hash != local_keccak256_file_hash:
306+
raise argparse.ArgumentTypeError(
307+
f"Error: Keccak256 checksum mismatch {soliditylang_platform()} - {version}"
283308
)
284309

285310

286-
def get_soliditylang_checksums(version: str) -> Tuple[str, str]:
311+
def get_soliditylang_checksums(version: str) -> Tuple[str, Optional[str]]:
287312
(_, list_url) = get_url(version=version)
288313
# pylint: disable=consider-using-with
289314
list_json = urllib.request.urlopen(list_url).read()
@@ -295,7 +320,16 @@ def get_soliditylang_checksums(version: str) -> Tuple[str, str]:
295320
f"Error: Unable to retrieve checksum for {soliditylang_platform()} - {version}"
296321
)
297322

298-
return matches[0]["sha256"], matches[0]["keccak256"]
323+
sha256_hash = matches[0]["sha256"]
324+
keccak256_hash = matches[0].get("keccak256")
325+
326+
# Normalize checksums by removing 0x prefix if present
327+
if sha256_hash and sha256_hash.startswith("0x"):
328+
sha256_hash = sha256_hash[2:]
329+
if keccak256_hash and keccak256_hash.startswith("0x"):
330+
keccak256_hash = keccak256_hash[2:]
331+
332+
return sha256_hash, keccak256_hash
299333

300334

301335
def get_url(version: str = "", artifact: str = "") -> Tuple[str, str]:
@@ -305,6 +339,18 @@ def get_url(version: str = "", artifact: str = "") -> Tuple[str, str]:
305339
CRYTIC_SOLC_ARTIFACTS + artifact,
306340
CRYTIC_SOLC_JSON,
307341
)
342+
elif sys.platform == "darwin" and get_arch() == "arm64":
343+
if version != "" and is_alloy_aarch64_version(version):
344+
return (
345+
ALLOY_SOLC_ARTIFACTS + artifact,
346+
ALLOY_SOLC_JSON,
347+
)
348+
else:
349+
# Fall back to Intel binaries for versions outside supported range
350+
return (
351+
f"https://binaries.soliditylang.org/{MACOSX_AMD64}/{artifact}",
352+
f"https://binaries.soliditylang.org/{MACOSX_AMD64}/list.json",
353+
)
308354
return (
309355
f"https://binaries.soliditylang.org/{soliditylang_platform()}/{artifact}",
310356
f"https://binaries.soliditylang.org/{soliditylang_platform()}/list.json",
@@ -379,6 +425,19 @@ def get_available_versions() -> Dict[str, str]:
379425
github_json = urllib.request.urlopen(list_url).read()
380426
additional_linux_versions = json.loads(github_json)["releases"]
381427
available_releases.update(additional_linux_versions)
428+
elif sys.platform == "darwin" and get_arch() == "arm64":
429+
# Fetch Alloy versions for ARM64 Darwin
430+
alloy_json = urllib.request.urlopen(ALLOY_SOLC_JSON).read()
431+
alloy_releases = json.loads(alloy_json)["releases"]
432+
# Filter to only include versions in the supported range (0.8.24+ are already universal)
433+
filtered_alloy_releases = {
434+
version: release
435+
for version, release in alloy_releases.items()
436+
if Version(ALLOY_ARM64_MIN_VERSION)
437+
<= Version(version)
438+
<= Version(ALLOY_ARM64_MAX_VERSION)
439+
}
440+
available_releases.update(filtered_alloy_releases)
382441

383442
return available_releases
384443

solc_select/utils.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,18 @@
77
from packaging.version import Version
88

99

10+
def get_arch() -> str:
11+
"""Get the current system architecture."""
12+
machine = platform.machine().lower()
13+
if machine in ["x86_64", "amd64"]:
14+
return "amd64"
15+
elif machine in ["aarch64", "arm64"]:
16+
return "arm64"
17+
elif machine in ["i386", "i686"]:
18+
return "386"
19+
return machine
20+
21+
1022
def mac_binary_is_universal(path: Path) -> bool:
1123
"""Check if the Mac binary is Universal or not. Will throw an exception if run on non-macOS."""
1224
assert sys.platform == "darwin"
@@ -17,6 +29,17 @@ def mac_binary_is_universal(path: Path) -> bool:
1729
return result.returncode == 0 and is_universal
1830

1931

32+
def mac_binary_is_native(path: Path):
33+
"""Check if the Mac binary matches the current system architecture. Will throw an exception if run on non-macOS."""
34+
assert sys.platform == "darwin"
35+
result = subprocess.run(["/usr/bin/file", str(path)], capture_output=True, check=False)
36+
output = result.stdout.decode()
37+
38+
arch_in_file = "arm64" if get_arch() == "arm64" else "x86_64"
39+
is_native = "Mach-O" in output and arch_in_file in output
40+
return result.returncode == 0 and is_native
41+
42+
2043
def mac_can_run_intel_binaries() -> bool:
2144
"""Check if the Mac is Intel or M1 with available Rosetta. Will throw an exception if run on non-macOS."""
2245
assert sys.platform == "darwin"

0 commit comments

Comments
 (0)