Solidity 0.8.0 is a breaking release of the Solidity compiler and language.

Some of the new features of this release have been elaborated in the 0.8.x preview release post. Please consider the preview release binary superseded and do not use it anymore.

Notable New Features and Changes

As per ususal, this breaking release does not include many features but rather changes that require a backwards-incompatible adjustment in syntax or semantics. For a detailed explanation, please see the documentation.

The change that will affect most users is that arithmetic operations are now checked by default, which means that overflow and underflow will cause a revert. This feature can be disabled locally by using an unchecked block.

The second change that is very visible is that the ABI coder v2 is activated by default. You can activate the old coder using pragma abicoder v1, or explicitly select v2 using pragma abicoder v2 - which has the same effect as pragma experimental ABIEncoderV2 had. ABI coder v2 is more complex than v1 but also performs additional checks on the input and supports a larger set of types than v1.

Furthermore, internal errors like division by zero, assertion failure and others do not use the invalid opcode anymore, but use revert with a special error message, in order to not waste gas in such situations.

Another important change is that we restricted the possibilities of explicit conversions to avoid ambiguities. All conversions that were possible before are still possible, but you might have to perform two conversions to get there - which does not affect the generated code, though.

These are only some of the important breaking changes in 0.8.0, please refer to the changelog below for the full list!

Checked Arithmetic

The “Checked Arithmetic” feature of Solidity 0.8.x consists of three sub-features:

  • Revert on assertion failures and similar conditions instead of using the invalid opcode.
  • Checked arithmetic, i.e., revert on overflows, underflows etc.
  • unchecked blocks.

Revert on assertion failures and similar conditions instead of using the invalid opcode

Previously, internal errors like division by zero, failing assertions, array access out of bounds, etc., would result in an invalid opcode being executed. The idea was to distinguish these more critical errors from non-critical errors that lie outside of the domain of the Smart Contract programmer like invalid input, not enough balance for a token transfer, and so on. These non-critical errors use the revert opcode and optionally add an error description.

The problem with the invalid opcode is that, in contrast to the revert opcode, it consumes all gas available. This makes it very expensive and you should try to avoid it at all cost.

We wanted to consider arithmetic overflow as a critical error, but did not want to cause it to consume all gas. As a middle ground, we chose to use the revert opcode, but provide different error data so that static (and dynamic) analysis tools can easily distinguish them.

More specifically:

  • Non-critical errors will revert either with empty error data or with error data that corresponds to the ABI-encoding of a function call to a function with the signature Error(string).
  • Critical errors revert with error data that corresponds to the ABI-encoding of a function call to a function with the signature Panic(uint256).

Because of that, we also use the term “panic” for such critical errors.

To distinguish the two situations, you can take a look at the first four bytes of the return data in case of a failure. If it is 0x4e487b71, then it is a panic, if it is 0x08c379a0 or if the message is empty, it is a “regular” error.

Panics use specific error codes to distinguish certain situations in which the panic is triggered. This is the current list of error codes:

  1. 0x01: If you call assert with an argument that evaluates to false.
  2. 0x11: If an arithmetic operation results in underflow or overflow outside of an unchecked { ... } block.
  3. 0x12: If you divide or divide modulo by zero (e.g. 5 / 0 or 23 % 0).
  4. 0x21: If you convert a value that is too big or negative into an enum type.
  5. 0x22: If you access a storage byte array that is incorrectly encoded.
  6. 0x31: If you call .pop() on an empty array.
  7. 0x32: If you access an array, bytesN or an array slice at an out-of-bounds or negative index (i.e. x[i] where i >= x.length or i < 0).
  8. 0x41: If you allocate too much memory or create an array that is too large.
  9. 0x51: If you call a zero-initialized variable of internal function type.

This list of codes can be extended in the future.

Note that you currently cannot use try ... catch Panic(uint _code) { ... } to catch a panic, but this is planned for the near future.

Checked arithmetic, i.e., revert on overflows, underflows etc.

By default, all arithmetic operations will perform overflow and underflow checks (Solidity already had division by zero checks). In case of an underflow or overflow, a Panic(0x11) error will be thrown and the call will revert.

The following code, for example, will trigger such an error:

contract C {
    function f() public pure {
        uint x = 0;
        x--;
    }
}

The checks are based on the actual type of the variable, so even if the result would fit an EVM word of 256 bits, if your type is uint8 and if the result is larger than 255, it will trigger a Panic.

We did not introduce checks for explicit type conversions from larger to smaller types, because we think that such checks might be unexpected. We would love to get your input on that matter!

The following operations (including their compound assignment versions like +=) now have checks that they did not have before:

  • addition (+), subtraction (-), multiplication (*)
  • increment and decrement (++ / --)
  • unary negation (-)
  • exponentiation (**)
  • division (see below) (/)

There are some edge cases you might not expect. For example, unary negation can trigger a panic on signed types. The problem is that in two’s complement representation, there is one more negative number for each bit width than there are positive numbers. This means that the following code will trigger a panic:

function f() pure {
    int8 x = -128;
    -x;
}

The reason is that 128 does not fit an 8-bit signed integer.

For the same reason, signed division can result in a panic:

function f() pure {
    int8 x = -128;
    x/(-1);
}

Everyone who wanted to write a SafeMath function for exponentiation probably noticed that it is rather expensive to do that because the EVM does not provide overflow signalling. Essentially you have to implement your own exp routine without using the exp opcode for the general case.

We hope that we found a rather efficient implementation and would also appreciate your feedback about that!

For many special cases, we actually implemented it using the exp opcode instead of our own implementation. More specifically, exponentiation operations that use a literal number as base will use the exp opcode directly. There are also specialized code paths for bases that are variables with small value. For bases up to 306, if the exponent is smaller than a hard-coded safe upper bound, it will use the exp opcode directly. If the base or the exponent is too large, it might fall back to the loop-based implementation.

unchecked Blocks

Since checked arithmetic uses more gas, as discussed in the previous section, and because there are times when you really want the wrapping behaviour, for example when implementing cryptographic routines, we provide a way to disable the checked arithmetic and re-enable the previous “wrapping” or “modulo” arithmetic:

All operations inside a block of the form unchecked { ... } use wrapping arithmetic. You can use this special block as a regular statement inside another block:

contract C {
    function f() public pure returns (uint) {
        uint x = 0;
        unchecked { x--; }
        return x;
    }
}

An unchecked block can contain any number or any kind of statement and will create its own variable scope. If there is a function call inside the unchecked block, the function will not inherit the setting - you have to use another unchecked block inside the function if you also want it to use wrapping arithmetic.

We decided for some restrictions for the unchecked block:

  1. In modifiers you cannot use _; inside an unchecked block, because this might lead to confusion about whether the property is inherited.

  2. You can only use it inside a block but not as a replacement for a block. The following code snippets, for example, are both invalid:

     function f() unchecked { uint x = 7; }
    
     function f() pure {
         uint x;
         for (uint i = 0; i < 100; i++) unchecked { x += i; }
     }
    

    You have to wrap the unchecked blocks in another block to make it work.

Explicit Conversions

Explicit conversions are only possible when there is at most one change in sign, width, payability or type-category (int, address, bytesNN, etc.).

For example, converting int8 to uint16 is only possible, if you go through uint8 or int16. Since the choice of the intermediate conversion will have an effect on the result we wanted to make it explicit:

  • uint16(int16(int8(-1))) results in 0xffff, while
  • uint16(uint8(int8(-1))) is 0xff.

There are more such changes in relation to payable addresses, literals, enums and contracts. For the full list of changes, please see the documentation.

General Remarks

Since we usually do not backport bugfixes, it is recommended to upgrade all code to be compatible with Solidity v0.8.0.

You can find a guide on how to update your code here.

Note that changes listed below are the changes between 0.7.6 and 0.8.0. For changes introduced during the 0.7.x series, please see the individual changelogs or release announcements on this blog.

Full Changelog

Breaking Changes:

  • Code Generator: All arithmetic is checked by default. These checks can be disabled using unchecked { ... }.
  • Code Generator: Cause a panic if a byte array in storage is accessed whose length is encoded incorrectly.
  • Code Generator: Use revert with error signature Panic(uint256) and error codes instead of invalid opcode on failing assertions.
  • Command Line Interface: JSON fields abi, devdoc, userdoc and storage-layout are now sub-objects rather than strings.
  • Command Line Interface: Remove the --old-reporter option.
  • Command Line Interface: Remove the legacy --ast-json option. Only the --ast-compact-json option is supported now.
  • General: Enable ABI coder v2 by default.
  • General: Remove global functions log0, log1, log2, log3 and log4.
  • Parser: Exponentiation is right associative. a**b**c is parsed as a**(b**c).
  • Scanner: Remove support for the \b, \f, and \v escape sequences.
  • Standard JSON: Remove the legacyAST option.
  • Type Checker: Function call options can only be given once.
  • Type System: Declarations with the name this, super and _ are disallowed, with the exception of public functions and events.
  • Type System: Disallow msg.data in receive() function.
  • Type System: Disallow type(super).
  • Type System: Disallow enums with more than 256 members.
  • Type System: Disallow explicit conversions from negative literals and literals larger than type(uint160).max to address type.
  • Type System: Disallow the byte type. It was an alias to bytes1.
  • Type System: Explicit conversion to address type always returns a non-payable address type. In particular, address(u), address(b), address(c) and address(this) have the type address instead of address payable (Here u, b, and c are arbitrary variables of type uint160, bytes20 and contract type respectively.)
  • Type System: Explicit conversions between two types are disallowed if it changes more than one of sign, width or kind at the same time.
  • Type System: Explicit conversions from literals to enums are only allowed if the value fits in the enum.
  • Type System: Explicit conversions from literals to integer type is as strict as implicit conversions.
  • Type System: Introduce address(...).code to retrieve the code as bytes memory. The size can be obtained via address(...).code.length, but it will currently always include copying the code.
  • Type System: Introduce block.chainid for retrieving the current chain id.
  • Type System: Support address(...).codehash to retrieve the codehash of an account.
  • Type System: The global variables tx.origin and msg.sender have type address instead of address payable.
  • Type System: Unary negation can only be used on signed integers, not on unsigned integers.
  • View Pure Checker: Mark chainid as view.
  • Yul: Disallow the use of reserved identifiers, such as EVM instructions, even if they are not available in the given dialect / EVM version.
  • Yul: The assignimmutable builtin in the “EVM with objects” dialect takes the base offset of the code to modify as an additional argument.

Language Features:

  • Super constructors can now be called using the member notation e.g. M.C(123).

Bugfixes:

  • Type Checker: Perform proper truncating integer arithmetic when using constants in array length expressions.

AST Changes:

  • New AST Node IdentifierPath replacing in many places the UserDefinedTypeName.
  • New AST Node UncheckedBlock used for unchecked { ... }.

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

Download the new version of Solidity here.