{ skip to content }

Solidity 0.8.22 Release Announcement

Posted by Solidity Team on October 25, 2023


We are excited to announce the release of the Solidity Compiler v0.8.22. This newest version of the compiler includes a range of language as well as compiler improvements such as file-level event definitions, optimizations for unchecked loop increments, support for importing EVM assembly JSON, & more.

Important Note

This release deprecates support for EVM versions older than Constantinople, which are becoming increasingly hard to maintain. These ancient versions have long been obsolete on the Ethereum mainnet and testnets and we suspect that they are no longer relevant for other networks as well. The complex code paths and workarounds slow down the development and testing of features targeting the new versions and we would like to stop supporting them in the future versions of the compiler. In case you rely on the support for such EVM versions, please reach out to us.

Notable New Features

Unchecked loop increments

The usage of unchecked arithmetic for incrementing for loop counters is common for the purpose of gas optimizations. Let’s take the example of the following loop with the counter i:

for (uint i = 0; i < array.length; ++i) {
    acc += array[i]; // i is not modified by the loop body

In a lot of cases (see below for precise conditions), the comparison will ensure that i never reaches the maximal value of its type and it is thus generally safe to assume that i will not overflow, as the loop will stop before that. In such a case, safety checking the counter would be redundant and a waste of gas. This encourages users to bypass the check using a verbose pattern which wraps the counter increment in an unchecked arithmetic block inside the loop body:

for (uint i = 0; i < array.length;) {
    acc += array[i];
    unchecked { i++; } // i gets incremented without overflow checks -- less gas used

Solidity 0.8.22 introduces an overflow check optimization that automatically generates an unchecked arithmetic increment of the counter of for loops. This new optimization removes the need for poor unchecked increment patterns in for loop bodies such as the one illustrated in the previous example.

In contrast, the new optimization enables users to return to the original, more readable code without sacrificing gas efficiency.

The precise conditions under which the new optimization avoids the overflow check are the following:

  • The loop condition is a comparison of the form i < ... for a local variable i (called "loop counter" from now on).
  • This comparison must be performed on the same type as the loop counter, i.e. the type of the right-hand-side must be implicitly convertible to the type of the loop counter, such that the loop counter is not implicitly widened before comparing.
  • The loop counter must be a local variable of a built-in integer type.
  • The loop expression must be a prefix or postfix increment of the loop counter, i.e. i++ or ++i.
  • The loop counter may not be modified in the loop condition or the loop body.

To clarify the second condition, consider the following code snippet:

for (uint8 i = 0; i < uint16(1000); ++i) {
    // loop body

In this case, i is converted to uint16 before the comparison and the condition is in fact never false, so the overflow check for the increment cannot be removed.

Also, note that < is the only comparison operator that will trigger the optimization. Operator <= and others are intentionally excluded. Additionally, the operator must be built-in - user-defined < is not eligible.

The optimization is straightforward and always beneficial, and is therefore enabled even when the rest of the optimizer is disabled using the general settings.optimizer.enabled setting. It can be explicitly turned off by setting settings.optimizer.details.simpleCounterForLoopUncheckedIncrement to false in Standard JSON input. It is not possible to disable it using the command-line interface.

Adjusting Yul optimizer to rematerialize zero literals

This release builds upon the support of the PUSH0 opcode introduced in version 0.8.20, by extending the Rematerialiser optimizer step to always rematerialize zero literals instead of leaving them stored as variable references, which in turn allows the use of PUSH0 instead of DUP in order to reduce gas costs. To make sure this is performed effectively, Rematerialiser and UnusedPruner steps were added to the default clean-up sequence of the Yul optimizer.

Adding support for importing EVM Assembly JSON (experimental)

This new release adds experimental support for importing EVM assembly, opening the possibility for external tools to perform super-optimizations just before the bytecode is emitted. The main purpose of this feature is to define a serialization format for the low-level EVM assembly that enables the assembly produced by the compiler be exported, modified, and re-imported, resuming the normal compilation process.

Important: This is an experimental feature and is not meant to be used in production at this stage. We are making this feature available as part of this release to allow you to try it out and provide feedback.

Allowing event definitions at file level

Solidity 0.8.22 allows you to define events at the file level. Event definitions can now be placed outside of the contract scope. This adds another option for code organisation, removing the need to artificially wrap events in libraries.

Additionally, this release also fixes a bug that previously resulted in an error during NatSpec generation for code that emits events defined in foreign contracts or interfaces. In the previous release (0.8.21), the Solidity compiler added support for qualified access to events defined in contracts and interfaces that the current contract does not inherit from, but the bug prevented the full use of the feature.

With this bugfix and file-level event definitions being allowed, the latest version of Solidity enables users to compile the following example without any errors:

interface I {
    event ForeignEvent();

contract C {
    event ForeignEvent();

event E();

contract D {
    function f() public {
        // Emitting a foreign event would trigger an internal error on 0.8.21
        emit I.ForeignEvent();
        emit C.ForeignEvent();
        // Emitting a file-level event. New feature.
        emit E();

Full Changelog

Language Features

  • Allow defining events at file level.

Compiler Features

  • Code Generator: Remove redundant overflow checks of certain for loops when the counter variable cannot overflow.
  • Commandline Interface: Add --no-import-callback option that prevents the compiler from loading source files not given explicitly on the CLI or in Standard JSON input.
  • Commandline Interface: Add an experimental --import-asm-json option that can import EVM assembly in the format used by --asm-json.
  • Commandline Interface: Use proper severity and coloring also for error messages produced outside of the compilation pipeline.
  • EVM: Deprecate support for "homestead", "tangerineWhistle", "spuriousDragon" and "byzantium" EVM versions.
  • Parser: Remove the experimental error recovery mode (--error-recovery / settings.parserErrorRecovery).
  • SMTChecker: Support user-defined operators.
  • Yul Optimizer: If PUSH0 is supported, favor zero literals over storing zero values in variables.
  • Yul Optimizer: Run the Rematerializer and UnusedPruner steps at the end of the default clean-up sequence.


  • Code Generator: Fix output from via-IR code generator being dependent on which files were discovered by import callback. In some cases, a different AST ID assignment would alter the order of functions in internal dispatch, resulting in superficially different but semantically equivalent bytecode.
  • NatSpec: Fix internal error when requesting userdoc or devdoc for a contract that emits an event defined in a foreign contract or interface.
  • SMTChecker: Fix encoding error that causes loops to unroll after completion.
  • SMTChecker: Fix inconsistency on constant condition checks when while or for loops are unrolled before the condition check.
  • Yul Optimizer: Fix replacement decisions during CSE being affected by Yul variable names generated by the compiler, resulting in different (but equivalent) bytecode in some situations.

AST Changes

  • AST: Fix wrong initial ID for Yul nodes in the AST.

How to install/upgrade?

To upgrade to the latest version of the Solidity Compiler, please follow the installation instructions available in our documentation.

You can download the new version of Solidity here: v0.8.22. If you want to build from the source code, do not use the source archives generated automatically by GitHub. Instead use solidity_0.8.22.tar.gz and take a look at our documentation on how to build from source.

We advise all Solidity developers to always upgrade to the latest version of Solidity in order to take advantage of improvements, optimizations, and most importantly, bug fixes.

Previous post

Next post

Get involved





Discover more

BlogDocumentationUse casesContributeAboutForum

2024 Solidity Team

Security Policy

Code of Conduct