We are excited to announce the latest release of the Solidity Compiler, Solidity v0.8.21.
Notable New Features
Stack-to-memory mover always enabled via-IR
The release addresses the issue of unoptimized code produced by the IR-based code generation pipeline being overly prone to "Stack Too Deep" errors. This is meant to help tools such as debuggers, which lose much of their effectiveness when working with optimized code.
The legacy pipeline can often avoid running out of reachable stack slots even in unoptimized mode, but this comes at the cost of higher complexity since many optimizations are hard-coded as a part of code generation. The new pipeline was designed from the beginning with the goal of separating these two concerns. It produces straightforward but very inefficient output that is meant to be refined in discrete steps by the Yul optimizer. The transformation performed by each step can be verified and reasoned about in isolation. The downside of this approach is that unoptimized code very often has lots of unused local variables and runs into stack issues.
Our solution is to pass the unoptimized code through minimal transformations that help with stack issues but do not change it significantly as full optimization would. Specifically, the compiler will now run UnusedPruner optimizer step on such code to remove unused variables. In addition, the stack-to-memory mover mechanism is now always enabled, resolving remaining stack issues, though at the cost of extra memory accesses.
Both of these things were already possible to achieve through existing optimizer settings but were not a part of the default behavior. Despite technically involving the optimizer module, we consider them an essential part of the new pipeline and the new baseline for what unoptimized code means.
Important Bugfixes
- Code Generator: Always generate code for the expression in <expression>.selector in the legacy code generation pipeline. You can read more about it in our blog post here.
- Yul Optimizer: Fix FullInliner step (i) not preserving the evaluation order of arguments passed into inlined functions in code that is not in expression-split form (i.e. when using a custom optimizer sequence in which the step not preceded by ExpressionSplitter (x)). You can read more about it in our blog post here.
Language Features
- Allow qualified access to events from other contracts.
- Relax restrictions on initialization of immutable variables. Reads and writes may now happen at any point at construction time outside of functions and modifiers. Explicit initialization is no longer mandatory.
Qualified access to foreign events
Solidity 0.8.21 enables access to events defined in contracts and interfaces that the current contract does not inherit from. As a result, the following example will now compile without errors:
library L { event EL(); } interface I { event EI(); } contract C { event EC(); } contract D { event ED(); } contract E is D { event EE(); function foo() public { emit L.EL(); emit I.EI(); // Not allowed on 0.8.20 emit C.EC(); // Not allowed on 0.8.20 emit ED(); emit EE(); } }
This used to be disallowed due to limitations of the NatSpec JSON format, which is a part of contract metadata. The format identifies events by their signatures, which include only unqualified names, and allows only one piece of NatSpec to be specified. Since event definitions with the same signature defined in different contracts represent the same event in the ABI, the correct solution is to include NatSpec associated with all available definitions, but such a change is not backward-compatible and has to wait for a breaking release.
In the meantime, this restriction was enough of a usability problem for language users that we decided to side-step it by arbitrarily ignoring NatSpec of one of the events. In case of a conflict, NatSpec of the definition present in the current contract or another contract in its inheritance hierarchy takes precedence. This will be corrected with the introduction of NatSpec v2.
Relaxed restrictions on initialization of immutable variables
Given the several bugs that were found in the past few months where our checks for immutable initialization were not up to par, and thus allowed several edge cases where said immutables could remain uninitialized (e.g., branches of try/catch blocks, for loops, etc.), we have decided to relax restrictions on the initialization of immutables significantly.
Whilst immutables can still only be explicitly initialized at construction time, this initialization is now optional, as all immutables will be default (zero) initialized regardless. We have also decided to lift the assign once restriction, which means you may now assign to an immutable more than once, with the last assignment being retained.
A significant downside of the mechanism responsible for enforcing these restrictions was that, out of necessity, it prevented some patterns of initialization that were perfectly safe but hard to verify. In a general case, precisely determining whether a variable is initialized on all possible paths through the construction code is not possible without false positives. To deal with this problem, the mechanism used simply disallowed initialization inside functions, modifiers, conditionals, loops and try/catch blocks. Now the restrictions on the latter three have been removed, and we hope to drop restrictions on initialization inside functions and modifiers as well, eventually.
The example below shows some of the ways to initialize immutables that are now possible.
contract C { uint immutable x; uint immutable y; uint immutable z; // Not initialized. Will be 0 constructor(bool condition, uint value) { x = value; if (condition) { x = 42; // Overwriting already assigned value z = 42; } else z = 24; // On 0.8.20 initialization inside an if is not allowed // even if there's no way for z to remain uninitialized } }
It is important to note that we are relaxing these restrictions because the checks were ultimately artificially imposing deployed contract immutability restrictions on contract creation for consistency and not for security reasons. Not all of the patterns that this enables are necessarily recommended, but the decision of whether they should be used is now in the hands of users.
Bug affecting the reproducibility of unoptimized bytecode from the IR pipeline
The release fixes a minor bug in the IR-based code generator, which resulted in different (but functionally equivalent) bytecode being produced depending on how the compiler binary was built. In particular, binaries built using the Clang C++ compiler would, in certain situations, order Yul functions differently than ones built with GCC. The case that triggers the bug is the indexing of memory arrays. This results in multiple Yul helpers being generated, their order dependent on the C++ compiler used.
The bug affected only unoptimized code generated by the IR pipeline, i.e. required the use of settings.viaIR in Standard JSON or the --via-ir flag on the CLI. The use of the Yul optimizer nullifies its effects since it reorders the functions.
The bug affects the official binaries provided in solc-bin. The emscripten and macOS binaries are built with Clang, Linux binaries with GCC, and Windows ones with MSVC. It is recommended that tools performing source verification of contracts consider the possibility of the deployed contract being produced by either of these binaries.
Full Changelog
Compiler Features
- Commandline Interface: Add --ast-compact-json output in assembler mode.
- Commandline Interface: Add --ir-ast-json and --ir-optimized-ast-json outputs for Solidity input, providing AST in compact JSON format for IR and optimized IR.
- Commandline Interface: Respect --optimize-yul and --no-optimize-yul in compiler mode and accept them in assembler mode as well. --optimize --no-optimize-yul combination now allows enabling EVM assembly optimizer without enabling Yul optimizer.
- EWasm: Remove EWasm backend.
- Parser: Introduce pragma experimental solidity, which will enable an experimental language mode that, in particular, has no stability guarantees between non-breaking releases and is not suited for production use.
- SMTChecker: Add --model-checker-print-query CLI option and settings.modelChecker.printQuery JSON option to output the SMTChecker queries in the SMTLIB2 format. This requires using smtlib2 solver only.
- Standard JSON Interface: Add ast file-level output for Yul input.
- Standard JSON Interface: Add irAst and irOptimizedAst contract-level outputs for Solidity input, providing AST in compact JSON format for IR and optimized IR.
- Yul Optimizer: Remove experimental ReasoningBasedSimplifier optimization step.
- Yul Optimizer: Stack-to-memory mover is now enabled by default whenever possible for via IR code generation and pure Yul compilation.
Bugfixes
- Code Generator: Disallow complex expressions whose results are types, built-ins, modules or some unassignable functions. The legacy code generation pipeline would not actually evaluate them, discarding any side effects they might have.
- Code Generator: Fix not entirely deterministic order of functions in unoptimized Yul output. The choice of C++ compiler in some cases would result in different (but equivalent) bytecode (especially from native binaries vs emscripten binaries).
- Commandline Interface: Fix internal error when using --stop-after parsing and requesting some of the outputs that require full analysis or compilation.
- Commandline Interface: It is no longer possible to specify both --optimize-yul and --no-optimize-yul at the same time.
- SMTChecker: Fix encoding of side-effects inside if and ternary conditionalstatements in the BMC engine.
- SMTChecker: Fix false negative when a verification target can be violated only by a trusted external call from another public function.
- SMTChecker: Fix generation of invalid SMT-LIB2 scripts in BMC engine with trusted mode for external calls when CHC engine times out.
- SMTChecker: Fix internal error caused by incorrectly classifying external function call using function pointer as a public getter.
- SMTChecker: Fix internal error caused by using an external identifier to encode member access to functions that take an internal function as a parameter.
- Standard JSON Interface: Fix an incomplete AST being returned when the analysis is interrupted by certain kinds of fatal errors.
- Type Checker: Disallow using certain unassignable function types in complex expressions.
- Type Checker: Function declaration types referring to different declarations are no longer convertible to each other.
- Yul Optimizer: Ensure that the assignment of memory slots for variables moved to memory does not depend on AST IDs that may depend on whether additional files are included during compilation.
- Yul Optimizer: Fix FullInliner step not ignoring code that is not in expression-split form.
- Yul Optimizer: Fix optimized IR being unnecessarily passed through the Yul optimizer again before bytecode generation.
AST Changes
- AST: Add the experimentalSolidity field to the SourceUnit nodes, which indicates whether the experimental parsing mode has been enabled via pragma experimental solidity.
How to install/upgrade
To upgrade to the latest version of the Solidity Compiler, simply follow the installation instructions available in our documentation. Our team has made sure to provide detailed and straightforward steps to make the upgrade process as seamless as possible. If you have any questions or run into any issues during the upgrade process, don't hesitate to reach out to our community Matrix channel.
You can download the new version of Solidity here: v0.8.21. If you want to build from the source code, do not use the source archives generated automatically by GitHub, instead please use solidity_0.8.21.tar.gz and take a look at our documentation on how to build from source if you need guidance.
Please note that for those using Solidity versions below 0.8.0, there are breaking changes. We highly recommend reviewing the detailed list of breaking changes in our documentation to ensure a smooth upgrade process. Additionally, we encourage all Solidity users to regularly check for updates to stay up-to-date with the latest improvements and optimizations. We advise all Solidity developers to upgrade to version 0.8.21 to take advantage of these improvements and optimizations.