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?
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.
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.
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.
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
- structs and enum declarations at file-level
calldataarguments in public functions,
memoryarguments in external ones
receive()function separate from
- 0.7.0 - 0.7.6
- file-level functions and constants
- even fewer restrictions on
- 0.8.0 - 0.8.10
- checked arithmetic (0.8.0)
- custom errors (0.8.4)
Panics that can be caught (0.8.1) and no longer eat all remaining gas on assertion failures (0.8.0)
- user-defined value types (0.8.8)
- support for
BASEFEEopcode introduced in the London fork (0.8.7)
@customNatspec tags (0.8.2)
verbatimbuiltin in Yul (0.8.5)
overridenot required when overriding interface functions (0.8.8)
immutablevariables in constructor (0.8.8)
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.