-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
solidity style guide 2.0 #10995
solidity style guide 2.0 #10995
Conversation
I see that you haven't updated any CHANGELOG files. Would it make sense to do so? |
contracts/STYLE_GUIDE.md
Outdated
import {IERC20} from "../../vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/IERC20.sol"; | ||
``` | ||
|
||
- Sorting imports alphabetically within the sections is nice, but since we don’t have any tooling to automatically do this it is not required. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's definitely an eslint rule for this. I'd say that this shouldn't be mentioned here, as otherwise someone who prefers this might make style comments, and these can be annoying.
Maybe it would be worthwhile to add a general rule of thumb that I use: "Anything to do with style should be a formatting plugin. If you care enough about a particular style, add or write that plugin, so that others wouldn't need to manually do this."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've split the doc into a Guidelines and a Rules section. All entries in the Rules section have a solhint rule
contracts/STYLE_GUIDE.md
Outdated
|
||
### Comments | ||
|
||
- Comments should be in the `//` (default) or `///` (natspec) format, not the `/* */` format. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... not the
/* */
format.
This should be a rule that automatically converts these comments to //
ones or warns/errors linting. I'd like to avoid having any style comments on PRs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds like a prettier thing, we can look into it but doesn't feel very high prio to me
contracts/STYLE_GUIDE.md
Outdated
import {IInterface} from "../interfaces/IInterface.sol"; | ||
|
||
import {AnythingElse} from "../code/AnythingElse.sol"; | ||
|
||
import {ThirdPartyCode} from "../../vendor/ThirdPartyCode.sol"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason for this particular spacing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These three groups indicate very different usage: interfaces are a black box import, only caring about external functions and not whats inside. Third party imports should imo always be clearly separated from the rest because of the fact that it isn't our code you're working with. That leaves "AnythingElse" in the middle, which is not black box and not third party, so it implies a closer relationship
contracts/STYLE_GUIDE.md
Outdated
|
||
- Comments should be in the `//` (default) or `///` (natspec) format, not the `/* */` format. | ||
- Comments should follow [NatSpec](https://docs.soliditylang.org/en/latest/natspec-format.html) | ||
- Besides comment above functions/structs, comments should live everywhere a reader might be confused. Don’t overestimate the reader of your contract, expect confusion in many places and document accordingly. This will help massively during audits and onboarding new team members. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like a general engineering best practice. It's useful to talk about it, but a styleguide might not be the first place people would look for it. Depends on how strict we are about what lives in a styleguide.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel a style guide like this is the correct place for this. Is there a meaningful difference between style and best practices?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall LGTM
- Don’t use headers for a single function, or to say “getters”. Group by functionality e.g. the `Tokens and pools` , or `fees` logic within the CCIP OnRamp. | ||
|
||
```solidity | ||
// ================================================================ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have a strong opinion on this, but should the comment formatting be part of the style guide?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like consistency and I've seen at least 4 formats to create headers in Chainlink code. Having a default helps imo
contracts/STYLE_GUIDE.md
Outdated
- Imports should follow the following format: | ||
|
||
```solidity | ||
import {IInterface} from "../interfaces/IInterface.sol"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where should external dependency imports (e.g. OpenZeppelin) be in terms of ordering?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At the bottom with ThirdPartyCode
contracts/STYLE_GUIDE.md
Outdated
# Testing | ||
|
||
- Test using Foundry, do not use Hardhat | ||
- Aim for 90%+ *useful* coverage as a baseline, but (near) 100% is achievable in Solidity. Always 100% test the critical path. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is 100% not achievable? Are there any specific examples that are hard/impossible to test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is, in non-solidity (imo) going for exactly 100 is often not extremely productive. Here I wanted to call out that in Solidity, 100% is achievable.
Well, if you exclude the fact that Foundry coverage sometimes doesn't work on libraries
Could we add a section on the topic of whether to prefer wrapped gas tokens vs. native? And how to determine the correct ERC-20 addresses? |
contracts/STYLE_GUIDE.md
Outdated
|
||
### Visibility | ||
|
||
- All contract variables should be private by default. Getters should be explicitly written and documented when you want to expose a variable publicly. Whether a getter function reads from storage, a constant, or calculates a value from somewhere else, that’s all implementation details that should not be exposed to the consumer by casing or other conventions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what are the implications of contract size here? e.g. gas efficiency for public variable vs (private var + public getter)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having variables public simply means a getter with the same name is added to the contract. I believe it is exactly the same gas and size-wise as writing one yourself.
contracts/STYLE_GUIDE.md
Outdated
function version() public returns(uint256) { | ||
return 4; | ||
} | ||
|
||
// solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables | ||
string public constant override typeAndVersion = "OffchainAggregator 1.0.0"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
didn't follow this. is the recommendation here to have both version()
and typeAndVersion
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The example contract was confusingly named. The function was just supposed to be example code and has been renamed now
contracts/STYLE_GUIDE.md
Outdated
|
||
### Visibility | ||
|
||
- Method visibility should always be explicitly declared. Contract’s [public interfaces should be tested](https://github.com/smartcontractkit/chainlink/blob/master/contracts/test/test-helpers/helpers.ts#L221) to enforce this and make sure that internal logic is not accidentally exposed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any guideline on private vs internal? Some audit guidelines suggest defaulting to internal, some suggest declaring internal only when needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All contract variables should be private by default
It will depend on the exact contract, if you the variable in higher layers, it has to be internal. Defaulting to private is the safest option
- Otherwise, Solidity contracts should have a pragma which is locked to a specific version. | ||
- Example: Most concrete contracts. | ||
- Avoid changing pragmas after audit. Unless there is a bug that has affects your contract, then you should try to stick to a known good pragma. In practice this means we typically only support one (occasionally two) pragma for any “major”(minor by semver naming) Solidity version. | ||
- The current advised pragma is `0.8.19` or higher, lower versions should be avoided when starting a new project. Newer versions can be considered. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When starting a new project, is picking the latest version advisable? Is there merit in picking an older, more battle tested version?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The latest that is already used in CL products would be default imo. The absolute latest (given it has been out for 3+ months) can be considered. There should be no reason so start any project with <0.8.19 as multiple products already use it. If it wasn't safe, we would not be using it
I think that isn't style related and very much dependent on what is being built and how. |
SonarQube Quality Gate |
Our preferences have evolved, and it's time to have the guide reflect that. This iterations aims to be more opinionated, leading to more consistent code.
After this guide has been approved, the solhint rule suite will be updated to enforce the new styling rules.