Solidity v0.8.13 fixes an important bug related to abi.encodeCall, extends the using for directive and implements "go to definition" for the language server.
Furthermore, compiling via the new Yul IR pipeline is now considered production ready.
Important Bugs
When abi.encodeCall was introduced in Solidity 0.8.11, hex literals (0x1234) and string literals ("abcd") were not handled properly. Please read more about it in the security alert.
Notable New Features
Yul IR Pipeline Production Ready
We have been working on Yul as an intermediate language for Solidity for several years now. Yul in itself has been considered production ready for a while - at least since ABI coder V2 was marked "stable", because it makes heavy use of Yul. The other part, code generation from Solidity to Yul, can now also be considered stable and production ready.
It has been feature complete for several months now. Since the beginning, we always ran all our tests through both compiler pipelines. Over the previous weeks, we added several more complex real-world contracts to our testing infrastructure and fixed some minor bugs in the component. The new code generator has been very stable for a long time now. The main reason for not marking it "non-experimental" earlier was that it still had problems with limited stack space, which we were able to fix now.
You can enable the new pipeline using the --via-ir commandline switch or by setting settings.viaIR to true in Standard JSON input (i.e. at the same level where you would have the optimizer key).
It is not recommended to use the Yul compiler pipeline without the optimizer because the unoptimized output is primarily meant to be a straightforward transformation of the high-level Solidity code and it has many additional checks compared to the old compiler pipeline. This makes it more modular and easier to audit while the new Yul-based optimizer is powerful enough to remove redundancies from the optimized version. Without the optimizer, you are also more likely to run into "stack too deep" issues which can often be avoided by the optimizer as explained below.
The performance of the new pipeline is not yet always superior to the old one, but it can do much higher-level optimization across functions, so please try it out and give us feedback!
Finally, in some edge cases, code compiled via the new compiler pipeline behaves differently than code compiled via the old compiler pipeline. The most prominent is the initialization order of state variables that depend on the result of constructors in other contracts. Others are related to weird uses of modifiers, dependence of evaluation order inside statements or dirty data. You can find a list of all the changes in our documentation.
Memory-Safe Assembly / Stack Too Deep
One important feature of the new compiler pipeline is that the Yul optimizer can move stack variables to memory and thus avoid the "stack too deep" issue in a lot of cases. In order to do this safely, the compiler needs to know that the memory slots it intends to use are not used by inline assembly.
Inline assembly is considered memory-safe if it only uses memory that has been previously allocated either by high-level Solidity code or by reading from the free memory pointer at 0x40. At the very beginning of the code, the compiler sets the free memory pointer to a position after the area reserved for the stack variables that are moved to memory.
The compiler considers inline assembly blocks as memory safe only for very simple blocks that do not have any opcodes that access memory and also do not access Solidity variables related to memory reference types. For all other blocks, it is difficult to impossible to determine this automatically in a safe way and thus we added a syntactic mechanism for the developer to assert it:
assembly ("memory-safe") { ... }
Marking an unsafe block as memory-safe can lead to undefined behavior due to conflicts between stack variables and memory. The compiler will not check that marked blocks are actually memory safe. Please see the documentation for more information about when an inline assembly block is memory safe.
Inline assembly blocks that access memory are not considered memory-safe by default and even a single such block disables the stack-to-memory mechanism for the whole contract. Assuming otherwise would be dangerous due to the risk of stack variables moved to memory being overwritten.
If you are e.g. writing a code library that is meant to be used across compiler versions, you can also use a special comment syntax that has the same effect:
/// @solidity memory-safe-assembly assembly { ... }
We advise against using this syntax, though. Please always use the most recent compiler version.
using for at File Level with Global Binding
This release extends the using for directive considerably. It can now be used at file level, it is possible to use it with free functions and it has the option to make its effect global. We believe that in combination with the recently released user-defined value types, this makes it possible to design nicely self-contained types.
In a future release, we plan to also add operators to user-defined types so that things like fixed-point type libraries are really easy to use and feel like compiler built-in types.
You can now use statements like using {f, g, L.h} for Type;, where f and g are free functions (defined at file level) and L.h is a library function.
You can also add the word global at the end to make the effect available everywhere. Since the idea is that you are defining a type and implementing the functions of the type, global can only be used for a user-defined type and only in the same file where the type is defined. We recommend that the using directive is always put directly after the definition of the type (forward references are valid in Solidity).
As an example, let us consider a type that can only be incremented and decremented by one at a time. Because of this restriction, we can safely drop overflow checks since it would take more gas than is available in the universe to reach an overflow.
File "restrictedNumber.sol":
type RestrictedNumber is int256; using {plusOne, minusOne} for RestrictedNumber global; function plusOne(RestrictedNumber x) pure returns (RestrictedNumber) { unchecked { return RestrictedNumber.wrap(RestrictedNumber.unwrap(x) + 1); } } function minusOne(RestrictedNumber x) pure returns (RestrictedNumber) { unchecked { return RestrictedNumber.wrap(RestrictedNumber.unwrap(x) - 1); } } /// This is a creation function that ensures that /// values are small enough. The idea is that the function /// RestrictedNumber.wrap should only be used in the file /// that defines the type, so that we have control over /// the invariants. function createRestrictedNumber(int256 value) pure returns (RestrictedNumber) { // Ensure that the number is "small". // Larger constants like 2**200 would also work. require(value <= 100 && -value <= 100); return RestrictedNumber.wrap(value); }
File "owned.sol":
import {RestrictedNumber} from "./restrictedNumber.sol"; contract Owned { RestrictedNumber public ownerCount; mapping(address => bool) public isOwner; constructor() { _addOwner(msg.sender); } function addOwner(address owner) external { require(isOwner[msg.sender]); _addOwner(owner); } function removeOwner(address owner) external { require(isOwner[msg.sender]); require(isOwner[owner]); // Because of `global`, we do not have to add // `using for` in the contract to use the // ``minusOne`` function. ownerCount = ownerCount.minusOne(); isOwner[owner] = false; } function _addOwner(address owner) internal { require(!isOwner[owner]); ownerCount = ownerCount.plusOne(); isOwner[owner] = true; } }
"Go to Definition" for LSP
The Solidity Language Server has been further improved and received one new feature: definition lookup of the underlying symbol.
This works on every symbol, such as local variables, return variables, type names, but also on import statements.
We consider the official Solidity Language Server still as a preview, that is, while it is already functional, there will be much more coming.
Please test it out and leave us some feedback in our GitHub issue tracker.
Full Changelog
Important Bugfixes:
- Code Generator: Correctly encode literals used in abi.encodeCall in place of fixed bytes arguments.
Language Features:
- General: Allow annotating inline assembly as memory-safe to allow optimizations and stack limit evasion that rely on respecting Solidity's memory model.
- General: using M for Type; is allowed at file level and M can now also be a brace-enclosed list of free functions or library functions.
- General: using ... for T global; is allowed at file level where the user-defined type T has been defined, resulting in the effect of the statement being available everywhere T is available.
Compiler Features:
- Commandline Interface: Allow the use of --via-ir in place of --experimental-via-ir.
- Compilation via Yul IR is no longer marked as experimental.
- JSON-AST: Added selector field for errors and events.
- LSP: Implements goto-definition.
- Peephole Optimizer: Optimize comparisons in front of conditional jumps and conditional jumps across a single unconditional jump.
- Yul EVM Code Transform: Avoid unnecessary pops on terminating control flow.
- Yul Optimizer: Remove sstore and mstore operations that are never read from.
Bugfixes:
- General: Fix internal error for locales with unusual capitalization rules. Locale set in the environment is now completely ignored.
- Type Checker: Fix incorrect type checker errors when importing overloaded functions.
- Yul IR Code Generation: Optimize embedded creation code with correct settings. This fixes potential mismatches between the constructor code of a contract compiled in isolation and the bytecode in type(C).creationCode, resp. the bytecode used for new C(...).
A big thank you to all contributors who helped make this release possible!
Download the new version of Solidity here.