Is there a rule on using a specific solidity version? I mostly work with 0.8.7 and they sometimes conflict with some Chainlink or Open Zeppelin contracts since they use either v6 or v7. Should I downgrade my solidity version or is there another approach? Are there any downsides of using a lower solidity version?
2 Answers
General recommendation
This depends on what your constraints are. If nothing is holding you back, especially if you're still learning the language or starting a new project that does not depend on anything old, it's best to follow the official recommendation:
When deploying contracts, you should use the latest released version of Solidity. Apart from exceptional cases, only the latest version receives security fixes. Furthermore, breaking changes as well as new features are introduced regularly. We currently use a 0.y.z version number to indicate this fast pace of change.
Security
From security perspective there's always the trade-off between new code fixing known issues and potentially introducing new ones. To make an informed choice you can always consult the List of Known Bugs. A new release never leaves known security issues unfixed. You might want to hold off for a few days after a new release just in case something is discovered once people start using it (like the (non-security-related) storage layout issue in 0.8.8 that was fixed by 0.8.9 released only 3 days later) but other than that it's recommended to stay as up to date as you can.
Having said that, it's not like tons of new security-related bugs are introduced in every new release. The last version with a known medium severity security issue was 0.8.2 and for one that could be classified as high you'd have to go as far as 0.4.24. Many of these problems only affect very specific usage patterns or language features, so if you're aware of them, it's possible to take countermeasures. There are tools like Slither that can detect many of them.
It must also be stated that the multi-million-dollar smart contract hacks you hear about all the time are in the vast majority of cases caused by undetected mistakes in contract code rather than exploits in the compiler. While it's entirely possible to run into a compiler bug, these in most cases result in an annoying but harmless Internal Compiler Error. Actual security holes and problems in the generated bytecode are the minority. You're far more likely to be affected by less strict syntactic and semantic checks, which is IMO, a much more important reason to use a recent compiler version.
Tools and libraries
A big factor that might keep you on an earlier version is tool and library support and this varies, especially around the time of a new breaking release. For example right now:
- Hardhat only officially supports versions up to 0.8.9 (you can still use it with a newer version but some features might not be prepared for changes introduced in it yet).
- Slither has released support for 0.8.x just yesterday and still does not recommend any version higher than 0.7.6 in its detector documentation.
- Libraries like OpenZeppelin always need time to adjust to breaking changes so they may sometimes lag behind bigger releases.
If your library of choice is still stuck at a particular version, there's unfortunately not much you can do other than using that version too or looking for a different library. Unless, of course, you're willing to contribute and help porting the library to a newer version. Usually it's just a matter of time and resources. If the library is no longer maintained though, I'd strongly recommend looking for an alternative.
Breaking changes
You surely noticed that many projects still use versions as low as the 0.5.x or even 0.4.x series. A big reason for this is that each minor version bump introduces backwards-incompatible changes and the upgrade is not always easy so in many cases projects do not update quickly if at all. An upgrade from, say, 0.5.10 to 0.5.17 might be effortless while jumping to 0.6.0 requires careful consideration and review of the whole code base.
The 0.4.x and 0.5.x series have been relatively long-lived while later the rate of breaking releases picked up. Let's take a look at the release dates in the Changelog:
- 0.4.x first released on 2016-09-08 (current for 2 years)
- 0.5.x first released on 2018-11-13 (current for 1 year)
- 0.6.x first released on 2019-12-17 (current for 8 months)
- 0.7.x first released on 2020-07-28 (current for 4 months)
- 0.8.x first released on 2020-12-16 (current for 1 year so far)
The upside of having more breaking releases is that each individual upgrade tends to carry fewer breaking changes and the migration is easier. For example upgrade from 0.7.x to 0.8.x in many cases is painless, only requiring some extra explicit conversions, and it's also much easier for libraries to support both versions at the same time than it used to be in the past.
New features
Solidity survey results show that many people skipped the 0.6.x and/or 0.7.x upgrade but are now upgrading to 0.8.x. Let's look at the highlights of each series:
- 0.6.0 - 0.6.12
immutable
variables- structs and enum declarations at file-level
calldata
arguments in public functions,memory
arguments in external onestry
/catch
statementvirtual
andoverride
keywordsreceive()
function separate fromfallback()
- 0.7.0 - 0.7.6
- file-level functions and constants
- even fewer restrictions on
calldata
arguments unicode
string literals
- 0.8.0 - 0.8.10
- checked arithmetic (0.8.0)
block.chainid
(0.8.0)Panic
s that can be caught (0.8.1) and no longer eat all remaining gas on assertion failures (0.8.0)@custom
Natspec tags (0.8.2)- custom errors (0.8.4)
bytes
<->bytesXX
conversion (0.8.5)verbatim
builtin in Yul (0.8.5)- support for
BASEFEE
opcode introduced in the London fork (0.8.7) - user-defined value types (0.8.8)
override
not required when overriding interface functions (0.8.8)- reading
immutable
variables in constructor (0.8.8) abi.encodeCall()
for type-safe ABI-encoding of calls (0.8.11)
A lot of these releases also fix bugs and introduce incremental improvements to the optimizer and SMTChecker, which add up over time. New versions sometimes add features needed to fully support newer EVM versions. Finally, the tendency is for the language to get stricter and more explicit which eliminates many classes of bugs that were easy to make in the past.
From the sheer number of new user-facing features it's pretty easy to see why 0.8.x finally forced many teams to upgrade. The lower you go, the more of these features you lose and most of them came to be only in 0.8.x.
-
@Kyle Baker I don't see much reason to sort the list of 0.8.x features by version. It's ordered from most to least prominent feature as I (subjectively) see it.– cameelJan 24, 2022 at 20:46
-
Well, most to least prominent is very subjective. However, ordering by version allows a user arriving at this question to browse through this list and see up to what version they might like to have. Given that it starts with 0.8.0, and versions are at the end, and the outer bullets are version-ordered, I naturally assumed it was ordered this way until I stared at it for a while. If I've convinced you, let me know, and I'll resubmit the edit... Jan 24, 2022 at 23:34
-
1@KyleBaker To be honest I don't mind either way. I rejected the edit because this was sorted like that on purpose and didn't need fixing in my opinion but if you feel that strongly about the order I can change it. Feel free to resubmit the edit.– cameelJan 27, 2022 at 22:24
Typically, the higher the version, the easier and better it is. So unless you have some specific constraints, you should use the newest version.
At least Hardhat supports projects which utilize different Solidity versions.