Solidity v0.8.8 introduces user defined value types as a major feature. The override keyword is now optional for interface functions, immutable variables can be read in the constructor, there is support for retrieving the smallest and largest value of an enum, you can specify include directories and the commandline interface was cleaned up. Furthermore, we fixed several bugs and the SMTChecker has improved language coverage.

Notable New Features

User Defined Value Types

A user defined value type allows creating a zero-cost-abstraction over an elementary value type that also increases type safety and improves readability. Details can be found in this deep dive blog post.

No Override for Interface Functions

A function that overrides only a single interface function does not require the override specifier.

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.8;

interface IMinimalERC20 {
     function transfer(address to, uint value) external;
}

contract MinimalERC20 is IMinimalERC20 {
     // Before version 0.8.8, this function declaration required
     // specifying the override keyword, i.e.,
     // `function transfer(address to, uint value) external override ...`
     function transfer(address to, uint value) external {
         // ...
     }
}

min and max for Enums

The smallest and the largest value of an enum type can now be accessed using type(E).min and type(E).max respectively.

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.8;

enum AuctionState {Created, InProgress, Completed}

// returns AuctionState.Created
function min() pure returns (AuctionState) {
    return type(AuctionState).min;
}

// returns AuctionState.Completed
function max() pure returns (AuctionState) {
    return type(AuctionState).max;
}

Reading From Immutable Variables

Immutable variables can be read at construction time once they are initialized.

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.8;

interface IMockInterface {
    function getValue() external returns(uint);
}

contract ImmutableExample {
    uint immutable value;

    event setValue(uint);

    constructor(IMockInterface mockContract) {
        // Sets the immutable variable.
        value = mockContract.getValue();
        // Reads the immutable variable.
        emit setValue(value);
    }
}

As you probably know, it is also possible to initialize immutable variables at the point of their declaration, for example uint immutable deployTime = block.timestamp;. Because the order of initialization is not always obvious, such immutable variables are only considered initialized once the constructor is executed. This means you can read from such variables inside the constructor but you cannot use them to initialize other immutable variables at the point of their declaration.

Import Include Paths

We added the new commandline option --include-path that hopefully makes it much easier to compile more complicated projects using multiple libraries. It is available in solcjs and solc both in legacy and standard-json mode. You can use it to specify directories where source files are looked up when import statements are resolved. If you use npm to install Solidity packages, they are usually placed in a subdirectory below node_modules. If you use imports of the form import X from "@packagename/contracts/x.sol";, then invoking the compiler as follows should properly resolve all imports without further work (the base path and the include paths will be added to the “allowed paths”, for example):

solc contract.sol --base-path . --include-path node_modules/

The previously introduced option --base-path provides a path where the lookup is performed first. If it fails, the include paths are searched for the file.

It is often a source of frustration that the Solidity compiler stores the full path to a source file as part of the metadata, which sometimes makes it more difficult to reproduce a compilation when not using the standard-json mode.

If you use --base-path and --include-path, then only relative paths to files located inside them will be stored in the metadata and thus should make it much easier to recompile the contracts to exactly the same binary on different systems.

On the other hand, if you provide any files to compile on the commandline that are not inside either the base path or an include path, these files will be named by their absolute path on your system and thus will end up like that in the metadata.

Currently, some projects use remappings to resolve dependencies and we believe that the “import path” feature is a better way to solve the same problem. Remappings are really only meant to change how the compiler sees its own internal file-system and to e.g. allow two different versions of the same library to be used in one contract or project.

Full Changelog

Language Features:

  • Inheritance: A function that overrides only a single interface function does not require the override specifier.
  • Type System: Support type(E).min and type(E).max for enums.
  • User Defined Value Type: allows creating a zero cost abstraction over a value type with stricter type requirements.

Compiler Features:

  • Commandline Interface: Add --include-path option for specifying extra directories that may contain importable code (e.g. packaged third-party libraries).
  • Commandline Interface: Do not implicitly run evm bytecode generation unless needed for the requested output.
  • Commandline Interface: Normalize paths specified on the command line and make them relative for files located inside base path and/or include paths.
  • Immutable variables can be read at construction time once they are initialized.
  • SMTChecker: Add constraints to better correlate address(this).balance and msg.value.
  • SMTChecker: Support constants via modules.
  • SMTChecker: Support low level call as external calls to unknown code.
  • SMTChecker: Support the value option for external function calls.
  • SMTChecker: Support user defined value types.

Bugfixes:

  • Code Generator: Fix ICE on assigning to calldata structs and statically-sized calldata arrays in inline assembly.
  • Code Generator: Use stable source order for ABI functions.
  • Commandline Interface: Disallow the --experimental-via-ir option in Standard JSON, Assembler and Linker modes.
  • Commandline Interface: Fix resolution of paths whitelisted with --allowed-paths or implicitly due to base path, remappings and files being compiled. Correctly handle paths that do not match imports exactly due to being relative, non-normalized or empty.
  • Commandline Interface: Report optimizer options as invalid in Standard JSON and linker modes instead of ignoring them.
  • Name Resolver: Fix that when importing an aliased symbol using import {AliasedName} from "a.sol" it would use the original name of the symbol and not the aliased one.
  • Opcode Optimizer: Prevent the optimizer from running multiple times to avoid potential bytecode differences for referenced code.
  • Parser: Properly check for multiple SPDX license identifiers next to each other and validate them.
  • SMTChecker: Fix BMC’s constraints regarding internal functions.
  • SMTChecker: Fix false negative caused by push on storage array references returned by internal functions.
  • SMTChecker: Fix false positive in external calls from constructors.
  • SMTChecker: Fix internal error on some multi-source uses of abi.*, cryptographic functions and constants.
  • Standard JSON: Fix non-fatal errors in Yul mode being discarded if followed by a fatal error.
  • Type Checker: Correct wrong error message in inline assembly complaining about .slot or .offset` not valid when actually.length`` was used.
  • Type Checker: Disallow modifier declarations and definitions in interfaces.
  • Yul Optimizer: Fix a crash in LoadResolver, when keccak256 has particular non-identifier arguments.

A big thank you to all contributors who helped make this release possible!

Download the new version of Solidity here.