diff --git a/docs/changelog.md b/docs/changelog.md index 6bb2bc4be..c13da62c0 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -6,6 +6,13 @@ small.label { } +## Unreleased + +Features & improvements: + +- added support for Solidity 0.8.35, including the `erc7201` builtin, `ast-id` debug annotations, the experimental `@future` EVM and SSA CFG pipeline, `yulCFGJson`, and Ethdebug standard JSON interfaces [core] +- fixed compiler checksum verification when a stable release and prerelease share a version number [core] + ## 4.22.1 (Mar 2, 2026) { id="4.22.1" } Fixes & improvements: diff --git a/docs/compilation.md b/docs/compilation.md index 195878b91..93cd21549 100644 --- a/docs/compilation.md +++ b/docs/compilation.md @@ -73,8 +73,8 @@ exclude_paths = ["node_modules", "venv", ".venv", "lib", "script", "test"] ## Via IR -The compiler can can generate bytecode by converting the sources to Yul first (`Solidity -> Yul -> EVM bytecode`) instead of the traditional `Solidity -> EVM bytecode` approach. -See the [Solidity documentation]() for more information. +The compiler can generate bytecode by converting the sources to Yul first (`Solidity -> Yul -> EVM bytecode`) instead of the traditional `Solidity -> EVM bytecode` approach. +See the [Solidity documentation](https://docs.soliditylang.org/en/latest/ir-breaking-changes.html) for more information. By default, the `via_IR` config option is left unset, which leaves the decision to the compiler. It can be enabled by setting the option to `true`: @@ -86,9 +86,32 @@ via_IR = true !!! note "`Stack too deep` errors" One way to avoid `Stack too deep` errors is to enable `via_IR` and the optimizer. +## Solidity 0.8.35 experimental features + +Solidity 0.8.35 introduced an explicit experimental mode, the experimental `@future` EVM target, and an experimental SSA CFG code generation pipeline. They can be enabled using: + +```toml title="wake.toml" +[compiler.solc] +target_version = "0.8.35" +experimental = true +evm_version = "@future" +via_SSA_CFG = true +``` + +`via_SSA_CFG` implies `via_IR`. Both `via_SSA_CFG` and `@future` require experimental mode. Experimental compiler features do not provide the same backwards-compatibility guarantees as stable Solidity features. + +Wake also supports the following Solidity 0.8.35 standard JSON additions through `wake.compiler.solc_frontend`: + +- `settings.debug.debugInfo` accepts `ast-id` annotations and experimental `ethdebug` annotations; +- `yulCFGJson` exposes the control-flow graph of the SSA-form Yul code; +- `evm.bytecode.ethdebug` and `evm.deployedBytecode.ethdebug` expose Ethdebug programs for creation and deployed bytecode; +- `ethdebug.resources` and `ethdebug.compilation` expose global Ethdebug resources and compilation information. + +Ethdebug is experimental, and its bytecode-level outputs can only be requested when compiling via IR. Requesting an Ethdebug output automatically enables the `ethdebug` debug annotation. Wake's IR also recognizes the Solidity 0.8.35 `erc7201` builtin used in storage layout expressions. + ## Optimizer -Wake allows setting all optimizer options supported by the Solidity compiler (see the [Solidity documentation](https://docs.soliditylang.org/en/v0.8.22/using-the-compiler.html#input-description)). +Wake allows setting all optimizer options supported by the Solidity compiler (see the [Solidity documentation](https://docs.soliditylang.org/en/latest/using-the-compiler.html#input-description)). By default, Wake leaves the `enabled` option unset, which leaves the decision to the compiler. It can be enabled by setting the option to `true`: ```toml title="wake.toml" diff --git a/docs/configuration.md b/docs/configuration.md index 7b76e3157..63714707d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -22,11 +22,13 @@ Wake can be configured using configuration options loaded from multiple sources [compiler.solc] allow_paths = [] # evm_version (unset - let the compiler decide) + # experimental (unset - disabled by the compiler) exclude_paths = ["node_modules", "venv", ".venv", "lib", "script", "test"] include_paths = ["node_modules"] remappings = [] # target_version (unset - use the latest version) # via_IR (unset - let the compiler decide) + # via_SSA_CFG (unset - let the compiler decide) [compiler.solc.optimizer] # enabled (unset - let the compiler decide) @@ -166,12 +168,14 @@ Additionally, detectors and printers may use this namespace to load needed API k | Option | Description | |:------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------| | `allow_paths` | Allow paths passed to the `solc` executable | -| `evm_version` | EVM version as specified by the [Solidity docs](https://docs.soliditylang.org/en/latest/using-the-compiler.html#target-options) | +| `evm_version` | EVM version as specified by the [Solidity docs](https://docs.soliditylang.org/en/latest/using-the-compiler.html#target-options); `@future` requires Solidity 0.8.35+ and experimental mode | +| `experimental` | Enable Solidity experimental mode; supported by Solidity 0.8.35 and newer | | `exclude_paths` | Files in these paths are not compiled unless imported from other non-excluded files | | `include_paths` | Paths (along with the current working directory) where files from non-relative imports are searched | | `remappings` | Compiler remappings as specified by the [Solidity docs](https://docs.soliditylang.org/en/latest/path-resolution.html#import-remapping) | | `target_version` | Target `solc` version used to compile the project | | `via_IR` | Compile the code via the Yul intermediate language (see the [Solidity docs](https://docs.soliditylang.org/en/latest/ir-breaking-changes.html)) | +| `via_SSA_CFG` | Compile through the experimental SSA CFG backend on Solidity 0.8.35+; implies `via_IR` and requires `experimental = true` | !!! info The `include_paths` option is the preferred way to handle imports of libraries. Remappings should be used only when `include_paths` cannot be used (e.g. when the import path differs from the system path of the imported file). diff --git a/docs/wake-schema.json b/docs/wake-schema.json index 57c7eea11..ba00ba825 100644 --- a/docs/wake-schema.json +++ b/docs/wake-schema.json @@ -35,8 +35,12 @@ }, "evm_version": { "type": "string", - "description": "Version of the EVM to compile for", - "enum": ["homestead", "tangerineWhistle", "spuriousDragon", "byzantium", "constantinople", "petersburg", "istanbul", "berlin", "london", "paris", "shanghai", "cancun", "prague", "osaka"] + "description": "Version of the EVM to compile for; @future requires Solidity 0.8.35+ and experimental mode", + "enum": ["homestead", "tangerineWhistle", "spuriousDragon", "byzantium", "constantinople", "petersburg", "istanbul", "berlin", "london", "paris", "shanghai", "cancun", "prague", "osaka", "@future"] + }, + "experimental": { + "type": "boolean", + "description": "Enable Solidity compiler experimental mode (Solidity 0.8.35+)" }, "exclude_paths": { "type": "array", @@ -104,6 +108,10 @@ "type": "boolean", "description": "Use new IR-based compiler pipeline" }, + "via_SSA_CFG": { + "type": "boolean", + "description": "Use the experimental SSA CFG code generation pipeline (Solidity 0.8.35+); implies via_IR and requires experimental mode" + }, "metadata": { "type": "object", "properties": { @@ -460,8 +468,12 @@ }, "evm_version": { "type": "string", - "description": "Version of the EVM to compile for", - "enum": ["homestead", "tangerineWhistle", "spuriousDragon", "byzantium", "constantinople", "petersburg", "istanbul", "berlin", "london", "paris", "shanghai", "cancun", "prague", "osaka"] + "description": "Version of the EVM to compile for; @future requires Solidity 0.8.35+ and experimental mode", + "enum": ["homestead", "tangerineWhistle", "spuriousDragon", "byzantium", "constantinople", "petersburg", "istanbul", "berlin", "london", "paris", "shanghai", "cancun", "prague", "osaka", "@future"] + }, + "experimental": { + "type": "boolean", + "description": "Enable Solidity compiler experimental mode (Solidity 0.8.35+)" }, "optimizer": { "type": "object", @@ -500,6 +512,10 @@ "type": "boolean", "description": "Use new IR-based compiler pipeline" }, + "via_SSA_CFG": { + "type": "boolean", + "description": "Use the experimental SSA CFG code generation pipeline (Solidity 0.8.35+); implies via_IR and requires experimental mode" + }, "metadata": { "type": "object", "properties": { @@ -515,4 +531,4 @@ } } } -} \ No newline at end of file +} diff --git a/tests/solidity_versions/solidity_0_8_35.sol b/tests/solidity_versions/solidity_0_8_35.sol new file mode 100644 index 000000000..e54cea9f5 --- /dev/null +++ b/tests/solidity_versions/solidity_0_8_35.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.35; + +contract Solidity0835 layout at erc7201("wake.test.solidity-0.8.35") { + uint256 public value; + + function assemblyValue() external pure returns (uint256 ret) { + assembly { + ret := 1 + } + } +} diff --git a/tests/test_compilation.py b/tests/test_compilation.py index 3b1605358..1f1370597 100644 --- a/tests/test_compilation.py +++ b/tests/test_compilation.py @@ -12,7 +12,18 @@ from wake.cli.__main__ import main from wake.compiler import SolcOutputSelectionEnum, SolidityCompiler +from wake.compiler.solc_frontend import ( + SolcFrontend, + SolcInputDebugInfoSettingsEnum, + SolcInputDebugSettings, + SolcInputOptimizerSettings, + SolcInputSettings, +) from wake.config import WakeConfig +from wake.core.enums import EvmVersionEnum +from wake.core.solidity_version import SolidityVersion +from wake.ir import ContractDefinition, FunctionCall, InlineAssembly +from wake.ir.enums import FunctionTypeKind, GlobalSymbol, InlineAssemblyEvmVersion from wake.utils import change_cwd PYTEST_BUILD_PATH = Path.home() / ".tmpwake_rkDv61DDf7" @@ -202,3 +213,106 @@ def test_compile_axelar(setup_project, config): }, ) assert cli_result.exit_code == 0 + + +@pytest.mark.slow +@pytest.mark.platform_dependent +def test_compile_solidity_0_8_35(tmp_path, monkeypatch): + monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path / "config")) + monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path / "data")) + source_path = ( + Path(__file__).parent / "solidity_versions" / "solidity_0_8_35.sol" + ).resolve() + project_root = Path(__file__).parent.parent.resolve() + config = WakeConfig.fromdict( + { + "compiler": { + "solc": { + "target_version": "0.8.35", + "experimental": True, + "evm_version": "@future", + "via_SSA_CFG": True, + "optimizer": {"enabled": True}, + } + } + }, + project_root_path=project_root, + ) + compiler = SolidityCompiler(config) + + build, errors = asyncio.run( + compiler.compile( + [source_path], + [SolcOutputSelectionEnum.ALL], + write_artifacts=False, + force_recompile=True, + ) + ) + + assert errors == set() + source_unit = build.source_units[source_path] + inline_assembly = next( + node for node in source_unit if isinstance(node, InlineAssembly) + ) + assert inline_assembly.evm_version == InlineAssemblyEvmVersion.FUTURE + contract = next( + declaration + for declaration in source_unit.declarations_iter() + if isinstance(declaration, ContractDefinition) + ) + assert contract.storage_layout is not None + base_slot_expression = contract.storage_layout.base_slot_expression + assert isinstance(base_slot_expression, FunctionCall) + assert base_slot_expression.function_called == GlobalSymbol.ERC7201 + assert base_slot_expression.expression.type.kind == FunctionTypeKind.ERC7201 + + frontend = SolcFrontend(config) + version = SolidityVersion.fromstring("0.8.35") + source = source_path.read_text() + ssa_output = asyncio.run( + frontend.compile( + {}, + {"C.sol": source}, + version, + SolcInputSettings( + experimental=True, + evm_version=EvmVersionEnum.FUTURE, + via_SSA_CFG=True, + optimizer=SolcInputOptimizerSettings(enabled=True), + output_selection={"*": {"*": [SolcOutputSelectionEnum.YUL_CFG_JSON]}}, + ), + ) + ) + assert ssa_output.contracts["C.sol"]["Solidity0835"].yul_CFG_json is not None + + ethdebug_output = asyncio.run( + frontend.compile( + {}, + {"C.sol": source}, + version, + SolcInputSettings( + experimental=True, + via_IR=True, + debug=SolcInputDebugSettings( + debug_info=[ + SolcInputDebugInfoSettingsEnum.AST_ID, + SolcInputDebugInfoSettingsEnum.ETHDEBUG, + ] + ), + optimizer=SolcInputOptimizerSettings(enabled=False), + output_selection={ + "*": { + "*": [ + SolcOutputSelectionEnum.EVM_BYTECODE_ETHDEBUG, + SolcOutputSelectionEnum.EVM_DEPLOYED_BYTECODE_ETHDEBUG, + SolcOutputSelectionEnum.ETHDEBUG_RESOURCES, + SolcOutputSelectionEnum.ETHDEBUG_COMPILATION, + ] + } + }, + ), + ) + ) + assert ethdebug_output.ethdebug is not None + assert ethdebug_output.ethdebug["resources"] is not None + assert ethdebug_output.ethdebug["compilation"] is not None diff --git a/tests/test_config.py b/tests/test_config.py index fa379144d..2de118906 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -4,6 +4,7 @@ import pydantic import pytest +from wake.cli.init import write_config from wake.config import WakeConfig from wake.config.data_model import SolcRemapping from wake.core.enums import EvmVersionEnum @@ -82,6 +83,34 @@ def test_config_from_dict(): assert config.compiler.solc.target_version == SolidityVersion.fromstring("0.8.12") +def test_config_solidity_0_8_35_experimental(tmp_path): + config = WakeConfig.fromdict( + { + "compiler": { + "solc": { + "evm_version": "@future", + "experimental": True, + "target_version": "0.8.35", + "via_SSA_CFG": True, + } + } + }, + project_root_path=tmp_path, + ) + + assert config.compiler.solc.evm_version == EvmVersionEnum.FUTURE + assert config.compiler.solc.experimental is True + assert config.compiler.solc.target_version == SolidityVersion.fromstring("0.8.35") + assert config.compiler.solc.via_SSA_CFG is True + + write_config(config) + written_config = config.local_config_path.read_text() + assert 'evm_version = "@future"' in written_config + assert "experimental = true" in written_config + assert 'target_version = "0.8.35"' in written_config + assert "via_SSA_CFG = true" in written_config + + @pytest.mark.platform_dependent def test_config_global(): os.environ["XDG_CONFIG_HOME"] = str(sources_path / "containing_global_conf") diff --git a/tests/test_solc_frontend.py b/tests/test_solc_frontend.py new file mode 100644 index 000000000..14e4b73ca --- /dev/null +++ b/tests/test_solc_frontend.py @@ -0,0 +1,176 @@ +import asyncio +import json + +import pytest + +from wake.compiler import SolcOutputSelectionEnum, SolidityCompiler +from wake.compiler.solc_frontend import ( + SolcFrontend, + SolcInput, + SolcInputDebugInfoSettingsEnum, + SolcInputSettings, + SolcOutput, +) +from wake.config import WakeConfig +from wake.core.enums import EvmVersionEnum +from wake.core.solidity_version import SolidityVersion + + +def test_solidity_0_8_35_standard_json_models(): + settings = SolcInputSettings( + experimental=True, + evm_version=EvmVersionEnum.FUTURE, + via_SSA_CFG=True, + ) + + assert settings.model_dump(by_alias=True, exclude_none=True) == { + "experimental": True, + "evmVersion": "@future", + "viaSSACFG": True, + } + + output = SolcOutput.model_validate( + { + "contracts": { + "C.sol": { + "C": { + "yulCFGJson": {}, + "evm": { + "bytecode": {"ethdebug": {}}, + "deployedBytecode": {"ethdebug": {}}, + }, + } + } + }, + "ethdebug": {"resources": {}, "compilation": {}}, + } + ) + + contract = output.contracts["C.sol"]["C"] + assert contract.yul_CFG_json == {} + assert contract.evm is not None + assert contract.evm.bytecode is not None + assert contract.evm.bytecode.ethdebug == {} + assert contract.evm.deployed_bytecode is not None + assert contract.evm.deployed_bytecode.ethdebug == {} + assert output.ethdebug == {"resources": {}, "compilation": {}} + + +@pytest.mark.parametrize( + ("value", "expected", "experimental"), + ( + ("ast-id", SolcInputDebugInfoSettingsEnum.AST_ID, False), + ("ethdebug", SolcInputDebugInfoSettingsEnum.ETHDEBUG, True), + ), +) +def test_solidity_0_8_35_debug_info_input(value, expected, experimental): + standard_input = SolcInput.model_validate_json( + json.dumps( + { + "language": "Solidity", + "sources": {"C.sol": {"content": "contract C {}"}}, + "settings": { + "experimental": experimental, + "debug": {"debugInfo": [value]}, + }, + } + ) + ) + + assert standard_input.settings is not None + assert standard_input.settings.debug is not None + assert standard_input.settings.debug.debug_info == [expected] + serialized = json.loads( + standard_input.model_dump_json(by_alias=True, exclude_none=True) + ) + assert serialized["settings"]["debug"] == {"debugInfo": [value]} + + +def test_global_experimental_outputs_remain_globally_scoped(tmp_path, monkeypatch): + monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path / "config")) + monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path / "data")) + config = WakeConfig(project_root_path=tmp_path) + compiler = SolidityCompiler(config) + settings = SolcInputSettings( + output_selection={ + "*": { + "": [SolcOutputSelectionEnum.AST], + "*": [ + SolcOutputSelectionEnum.ABI, + SolcOutputSelectionEnum.ETHDEBUG_RESOURCES, + SolcOutputSelectionEnum.ETHDEBUG_COMPILATION, + ], + } + } + ) + + optimized = compiler.optimize_build_settings(settings, {"C.sol"}) + + assert optimized.output_selection == { + "*": { + "": [SolcOutputSelectionEnum.AST], + "*": [ + SolcOutputSelectionEnum.ETHDEBUG_RESOURCES, + SolcOutputSelectionEnum.ETHDEBUG_COMPILATION, + ], + }, + "C.sol": {"*": [SolcOutputSelectionEnum.ABI]}, + } + + +def test_compiler_build_settings_include_experimental_config(tmp_path, monkeypatch): + monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path / "config")) + monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path / "data")) + config = WakeConfig.fromdict( + { + "compiler": { + "solc": { + "experimental": True, + "evm_version": "@future", + "via_SSA_CFG": True, + } + } + }, + project_root_path=tmp_path, + ) + + settings = SolidityCompiler(config).create_build_settings([], None) + + assert settings.experimental is True + assert settings.evm_version == EvmVersionEnum.FUTURE + assert settings.via_SSA_CFG is True + + +def test_solidity_0_8_35_settings_are_not_forwarded_to_older_compilers( + tmp_path, monkeypatch +): + monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path / "config")) + monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path / "data")) + config = WakeConfig(project_root_path=tmp_path) + captured = {} + + async def run_solc(_self, target_version, standard_input): + captured["target_version"] = target_version + captured["settings"] = standard_input.settings + return SolcOutput() + + monkeypatch.setattr(SolcFrontend, "_SolcFrontend__run_solc", run_solc) + frontend = SolcFrontend(config) + + asyncio.run( + frontend.compile( + {}, + {"C.sol": "pragma solidity 0.8.34; contract C {}"}, + SolidityVersion.fromstring("0.8.34"), + SolcInputSettings( + experimental=True, + evm_version=EvmVersionEnum.FUTURE, + via_SSA_CFG=True, + ), + ) + ) + + assert captured["target_version"] == SolidityVersion.fromstring("0.8.34") + assert captured["settings"].experimental is None + assert captured["settings"].via_SSA_CFG is None + assert captured["settings"].evm_version == EvmVersionEnum.OSAKA diff --git a/tests/test_svm.py b/tests/test_svm.py index 8f01fd2c2..19168e663 100644 --- a/tests/test_svm.py +++ b/tests/test_svm.py @@ -1,4 +1,5 @@ import asyncio +import hashlib import os import shutil import subprocess @@ -7,10 +8,11 @@ import aiohttp import pytest +from Crypto.Hash import keccak from wake.config import WakeConfig -from wake.svm import SolcVersionManager from wake.svm.exceptions import UnsupportedVersionError +from wake.svm.svm import SolcBuilds, SolcVersionManager PYTEST_WAKE_PATH = Path.home() / ".tmpwake_KVUhSovO5J" PYTEST_WAKE_PATH2 = Path.home() / ".tmpwake2_fLtqXkHeVH" @@ -161,3 +163,56 @@ async def test_file_executable(run_cleanup, config): await svm.install(oldest_version) output = subprocess.check_output([str(svm.get_path(oldest_version)), "--version"]) assert str(oldest_version).encode("utf-8") in output + + +def test_stable_build_checksum_selected_when_prerelease_exists(tmp_path, monkeypatch): + monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path / "config")) + monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path / "data")) + + stable_content = b"stable compiler" + prerelease_content = b"prerelease compiler" + + def checksums(content: bytes): + keccak256 = keccak.new(digest_bits=256) + keccak256.update(content) + return f"0x{keccak256.hexdigest()}", f"0x{hashlib.sha256(content).hexdigest()}" + + prerelease_keccak, prerelease_sha = checksums(prerelease_content) + stable_keccak, stable_sha = checksums(stable_content) + stable_filename = "solc-v0.8.35+commit.stable" + + config = WakeConfig(project_root_path=tmp_path) + svm = SolcVersionManager(config) + builds = SolcBuilds.model_validate( + { + "builds": [ + { + "path": "solc-v0.8.35-pre.1+commit.prerelease", + "version": "0.8.35", + "build": "commit.prerelease", + "longVersion": "0.8.35-pre.1+commit.prerelease", + "keccak256": prerelease_keccak, + "sha256": prerelease_sha, + "urls": [], + }, + { + "path": stable_filename, + "version": "0.8.35", + "build": "commit.stable", + "longVersion": "0.8.35+commit.stable", + "keccak256": stable_keccak, + "sha256": stable_sha, + "urls": [], + }, + ], + "releases": {"0.8.35": stable_filename}, + "latestRelease": "0.8.35", + } + ) + setattr(svm, "_SolcVersionManager__solc_builds", builds) + + compiler_path = svm.get_path("0.8.35") + compiler_path.parent.mkdir(parents=True) + compiler_path.write_bytes(stable_content) + + assert svm.installed("0.8.35") diff --git a/wake/cli/init.py b/wake/cli/init.py index e46945488..dbe1bc3da 100644 --- a/wake/cli/init.py +++ b/wake/cli/init.py @@ -61,9 +61,17 @@ def write_config(config: WakeConfig) -> None: if config.compiler.solc.evm_version is not None: f.write(f'evm_version = "{config.compiler.solc.evm_version}"\n') + if config.compiler.solc.experimental is not None: + f.write( + f"experimental = {str(config.compiler.solc.experimental).lower()}\n" + ) + if config.compiler.solc.via_IR is not None: f.write(f"via_IR = {str(config.compiler.solc.via_IR).lower()}\n") + if config.compiler.solc.via_SSA_CFG is not None: + f.write(f"via_SSA_CFG = {str(config.compiler.solc.via_SSA_CFG).lower()}\n") + if config.compiler.solc.target_version is not None: f.write(f'target_version = "{config.compiler.solc.target_version}"\n') diff --git a/wake/cli/open.py b/wake/cli/open.py index 2ea5e3a5b..ae9486753 100644 --- a/wake/cli/open.py +++ b/wake/cli/open.py @@ -210,6 +210,10 @@ async def open_address( if "viaIR" in c: config_dict["compiler"]["solc"]["via_IR"] = c["viaIR"] + if "experimental" in c: + config_dict["compiler"]["solc"]["experimental"] = c["experimental"] + if "viaSSACFG" in c: + config_dict["compiler"]["solc"]["via_SSA_CFG"] = c["viaSSACFG"] else: compiler_version: str = info["CompilerVersion"] if compiler_version.startswith("vyper"): @@ -250,6 +254,14 @@ async def open_address( config_dict["compiler"]["solc"][ "via_IR" ] = standard_input.settings.via_IR + if standard_input.settings.experimental is not None: + config_dict["compiler"]["solc"][ + "experimental" + ] = standard_input.settings.experimental + if standard_input.settings.via_SSA_CFG is not None: + config_dict["compiler"]["solc"][ + "via_SSA_CFG" + ] = standard_input.settings.via_SSA_CFG if standard_input.settings.remappings is not None: config_dict["compiler"]["solc"][ "remappings" diff --git a/wake/compiler/compiler.py b/wake/compiler/compiler.py index 8c3e0e3be..18d977d42 100644 --- a/wake/compiler/compiler.py +++ b/wake/compiler/compiler.py @@ -622,7 +622,9 @@ def create_build_settings( str(remapping) for remapping in self.__config.compiler.solc.remappings ] settings.evm_version = solc_settings.evm_version + settings.experimental = solc_settings.experimental settings.via_IR = solc_settings.via_IR + settings.via_SSA_CFG = solc_settings.via_SSA_CFG settings.optimizer = SolcInputOptimizerSettings( enabled=solc_settings.optimizer.enabled, runs=solc_settings.optimizer.runs, @@ -679,11 +681,31 @@ def optimize_build_settings( and "*" in settings.output_selection["*"] ): new_selection = {} + global_output_types = { + SolcOutputSelectionEnum.ETHDEBUG_RESOURCES, + SolcOutputSelectionEnum.ETHDEBUG_COMPILATION, + } + selected_output_types = settings.output_selection["*"]["*"] + global_outputs = [ + output_type + for output_type in selected_output_types + if output_type in global_output_types + ] + contract_outputs = [ + output_type + for output_type in selected_output_types + if output_type not in global_output_types + ] + if "" in settings.output_selection["*"]: new_selection["*"] = {"": settings.output_selection["*"][""]} - for source_unit in modified_source_units: - new_selection[source_unit] = {"*": settings.output_selection["*"]["*"]} + if len(global_outputs) > 0: + new_selection.setdefault("*", {})["*"] = global_outputs + + if len(contract_outputs) > 0: + for source_unit in modified_source_units: + new_selection[source_unit] = {"*": contract_outputs} ret = settings.model_copy() ret.output_selection = new_selection diff --git a/wake/compiler/solc_frontend/input_data_model.py b/wake/compiler/solc_frontend/input_data_model.py index dd1f5afe8..d28f0b9e1 100644 --- a/wake/compiler/solc_frontend/input_data_model.py +++ b/wake/compiler/solc_frontend/input_data_model.py @@ -49,6 +49,8 @@ class SolcOutputSelectionEnum(StrEnum): """Old-style assembly format in JSON""" EVM_BYTECODE_ALL = "evm.bytecode" """All bytecode subassets""" + EVM_BYTECODE_ETHDEBUG = "evm.bytecode.ethdebug" + """Experimental Ethdebug program for creation bytecode""" EVM_BYTECODE_FUNCTION_DEBUG_DATA = "evm.bytecode.functionDebugData" """Debugging information at function level""" EVM_BYTECODE_OBJECT = "evm.bytecode.object" @@ -63,6 +65,8 @@ class SolcOutputSelectionEnum(StrEnum): """Sources generated by the compiler""" EVM_DEPLOYED_BYTECODE_ALL = "evm.deployedBytecode" """All deployed bytecode subassets""" + EVM_DEPLOYED_BYTECODE_ETHDEBUG = "evm.deployedBytecode.ethdebug" + """Experimental Ethdebug program for deployed bytecode""" EVM_DEPLOYED_BYTECODE_FUNCTION_DEBUG_DATA = "evm.deployedBytecode.functionDebugData" """Debugging information at function level""" EVM_DEPLOYED_BYTECODE_OBJECT = "evm.deployedBytecode.object" @@ -83,6 +87,12 @@ class SolcOutputSelectionEnum(StrEnum): """The list of function hashes""" EVM_GAS_ESTIMATES = "evm.gasEstimates" """Function gas estimates""" + YUL_CFG_JSON = "yulCFGJson" + """Experimental control-flow graph of the SSA-form Yul code""" + ETHDEBUG_RESOURCES = "ethdebug.resources" + """Experimental global Ethdebug resources output""" + ETHDEBUG_COMPILATION = "ethdebug.compilation" + """Experimental global Ethdebug compilation output""" EWASM_ALL = "ewasm" """All EWASM subassets""" EWASM_WAST = "ewasm.wast" @@ -200,6 +210,10 @@ class SolcInputDebugInfoSettingsEnum(StrEnum): """ SNIPPET = "snippet" """A single-line code snippet from the location indicated by `@src`. The snippet is quoted and follows the corresponding `@src` annotation.""" + AST_ID = "ast-id" + """Annotations of the form `@ast-id ` for elements mapped to definitions in the Solidity AST.""" + ETHDEBUG = "ethdebug" + """Experimental Ethdebug annotations.""" class SolcInputDebugSettings(SolcInputModel): @@ -227,9 +241,11 @@ class SolcInputModelCheckerSettings(SolcInputModel): class SolcInputSettings(SolcInputModel): stop_after: Optional[SolcStopAfterEnum] = None remappings: Optional[List[str]] = None + experimental: Optional[bool] = None optimizer: Optional[SolcInputOptimizerSettings] = None evm_version: Optional[EvmVersionEnum] = None via_IR: Optional[bool] = Field(None, alias="viaIR") + via_SSA_CFG: Optional[bool] = Field(None, alias="viaSSACFG") debug: Optional[SolcInputDebugSettings] = None metadata: Optional[SolcInputMetadataSettings] = None libraries: Optional[ diff --git a/wake/compiler/solc_frontend/output_data_model.py b/wake/compiler/solc_frontend/output_data_model.py index a59fae174..7061686c5 100644 --- a/wake/compiler/solc_frontend/output_data_model.py +++ b/wake/compiler/solc_frontend/output_data_model.py @@ -1,6 +1,6 @@ from typing import Dict, List, Optional, Tuple -from pydantic import BaseModel, ConfigDict +from pydantic import BaseModel, ConfigDict, Field from wake.utils import StrEnum @@ -191,6 +191,7 @@ class SolcOutputEvmBytecodeLinkReferencesInfo(SolcOutputModel): class SolcOutputEvmBytecodeData(SolcOutputModel): + ethdebug: Optional[Dict] = None function_debug_data: Optional[ Dict[str, SolcOutputEvmBytecodeFunctionDebugData] ] = None # internal name of the function -> debug data @@ -247,6 +248,8 @@ class SolcOutputContractInfo(SolcOutputModel): """See https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html#json-output""" transient_storage_layout: Optional[SolcOutputStorageLayout] = None """See https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html#json-output""" + yul_CFG_json: Optional[Dict] = Field(None, alias="yulCFGJson") + """Experimental control-flow graph of the SSA-form Yul code""" evm: Optional[SolcOutputEvmData] = None """EVM-related outputs""" ewasm: Optional[SolcOutputEwasmData] = None @@ -255,6 +258,7 @@ class SolcOutputContractInfo(SolcOutputModel): class SolcOutput(SolcOutputModel): auxiliary_input_requested: Optional[Dict] = None + ethdebug: Optional[Dict] = None errors: List[SolcOutputError] = [] sources: Dict[str, SolcOutputSourceInfo] = {} contracts: Dict[ diff --git a/wake/compiler/solc_frontend/solc_runner.py b/wake/compiler/solc_frontend/solc_runner.py index 1e4f12cec..1f1bf6584 100644 --- a/wake/compiler/solc_frontend/solc_runner.py +++ b/wake/compiler/solc_frontend/solc_runner.py @@ -32,6 +32,7 @@ SolidityVersion.fromstring("0.8.24"): EvmVersionEnum.CANCUN, SolidityVersion.fromstring("0.8.27"): EvmVersionEnum.PRAGUE, SolidityVersion.fromstring("0.8.29"): EvmVersionEnum.OSAKA, + SolidityVersion.fromstring("0.8.35"): EvmVersionEnum.FUTURE, } @@ -87,6 +88,21 @@ async def compile( ) standard_input.settings.via_IR = None + if target_version < "0.8.35": + if settings.experimental is not None: + if settings.experimental: + logger.warning( + "`experimental` is not supported for solc versions < 0.8.35. This option will be ignored." + ) + standard_input.settings.experimental = None + + if settings.via_SSA_CFG is not None: + if settings.via_SSA_CFG: + logger.warning( + "`via_SSA_CFG` is not supported for solc versions < 0.8.35. This option will be ignored." + ) + standard_input.settings.via_SSA_CFG = None + if settings.evm_version is not None: # find nearest <= version in MAX_SUPPORTED_EVM_VERSIONS nearest_version = max( diff --git a/wake/config/data_model.py b/wake/config/data_model.py index 0c6cfac86..abd883819 100644 --- a/wake/config/data_model.py +++ b/wake/config/data_model.py @@ -130,6 +130,8 @@ class SolcConfig(WakeConfigModel): """Wake should set solc `--allow-paths` automatically. This option allows to specify additional allowed paths.""" evm_version: Optional[EvmVersionEnum] = None """Version of the EVM to compile for. Leave unset to let the solc decide.""" + experimental: Optional[bool] = None + """Enable Solidity compiler experimental mode.""" exclude_paths: FrozenSet[PurePath] = Field( default_factory=lambda: frozenset( [ @@ -173,6 +175,10 @@ class SolcConfig(WakeConfigModel): """ Use new IR-based compiler pipeline. """ + via_SSA_CFG: Optional[bool] = None + """ + Use the experimental SSA CFG code generation pipeline. Implies `via_IR`. + """ metadata: SolcMetadataConfig = Field(default_factory=SolcMetadataConfig) """ Metadata config options. @@ -191,8 +197,10 @@ class SubprojectConfig(WakeConfigModel): paths: FrozenSet[PurePath] = frozenset() target_version: Optional[SolidityVersion] = None evm_version: Optional[EvmVersionEnum] = None + experimental: Optional[bool] = None optimizer: SolcOptimizerConfig = Field(default_factory=SolcOptimizerConfig) via_IR: Optional[bool] = None + via_SSA_CFG: Optional[bool] = None metadata: SolcMetadataConfig = Field(default_factory=SolcMetadataConfig) _normalize_paths = field_validator("paths", mode="before")(normalize_paths) diff --git a/wake/config/wake_config.py b/wake/config/wake_config.py index dd52cb61e..feb01f912 100644 --- a/wake/config/wake_config.py +++ b/wake/config/wake_config.py @@ -536,7 +536,7 @@ def max_solidity_version(self) -> SolidityVersion: Returns: Maximum supported Solidity version. """ - return SolidityVersion.fromstring("0.8.34") + return SolidityVersion.fromstring("0.8.35") @property def detectors(self) -> DetectorsConfig: diff --git a/wake/core/enums.py b/wake/core/enums.py index b609f5a9f..71e164317 100644 --- a/wake/core/enums.py +++ b/wake/core/enums.py @@ -16,6 +16,7 @@ class EvmVersionEnum(StrEnum): CANCUN = "cancun" PRAGUE = "prague" OSAKA = "osaka" + FUTURE = "@future" def __lt__(self, other: "EvmVersionEnum") -> bool: if not isinstance(other, EvmVersionEnum): @@ -54,4 +55,5 @@ def __ge__(self, other: "EvmVersionEnum") -> bool: EvmVersionEnum.CANCUN, EvmVersionEnum.PRAGUE, EvmVersionEnum.OSAKA, + EvmVersionEnum.FUTURE, ] diff --git a/wake/development/utils.py b/wake/development/utils.py index 2b9d97353..324f9d1b5 100644 --- a/wake/development/utils.py +++ b/wake/development/utils.py @@ -1732,6 +1732,20 @@ def _get_storage_layout_from_explorer( "remappings" ] = standard_input.settings.remappings + if standard_input.settings is not None: + if standard_input.settings.experimental is not None: + config_dict["compiler"]["solc"][ + "experimental" + ] = standard_input.settings.experimental + if standard_input.settings.via_IR is not None: + config_dict["compiler"]["solc"][ + "via_IR" + ] = standard_input.settings.via_IR + if standard_input.settings.via_SSA_CFG is not None: + config_dict["compiler"]["solc"][ + "via_SSA_CFG" + ] = standard_input.settings.via_SSA_CFG + if any( source.urls is not None for source in standard_input.sources.values() ): diff --git a/wake/ir/enums.py b/wake/ir/enums.py index 95f3502ba..641168db6 100644 --- a/wake/ir/enums.py +++ b/wake/ir/enums.py @@ -38,6 +38,7 @@ class GlobalSymbol(IntEnum): TYPE = -27 THIS = -28 BLOBHASH = -29 + ERC7201 = -30 BLOCK_BASEFEE = -100 BLOCK_CHAINID = -101 @@ -318,6 +319,7 @@ class InlineAssemblyEvmVersion(StrEnum): CANCUN = "cancun" PRAGUE = "prague" OSAKA = "osaka" + FUTURE = "@future" class InlineAssemblySuffix(StrEnum): @@ -345,6 +347,7 @@ class FunctionTypeKind(StrEnum): SEND = "send" TRANSFER = "transfer" KECCAK256 = "keccak256" + ERC7201 = "erc7201" SELFDESTRUCT = "selfdestruct" REVERT = "revert" EC_RECOVER = "ecrecover" diff --git a/wake/svm/svm.py b/wake/svm/svm.py index d59c0588a..e00560c9b 100644 --- a/wake/svm/svm.py +++ b/wake/svm/svm.py @@ -344,10 +344,10 @@ def __fetch_list_file( def __verify_checksums(self, version: SolidityVersion) -> bool: assert self.__solc_builds is not None - build_info = next(b for b in self.__solc_builds.builds if b.version == version) + filename = self.__solc_builds.releases[version] + build_info = next(b for b in self.__solc_builds.builds if b.path == filename) local_path = self.get_path(version) - filename = self.__solc_builds.releases[version] if filename.endswith(".zip"): local_path = local_path.parent / filename if not local_path.is_file():