Skip to content
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

Merged
merged 3 commits into from
Nov 10, 2023
Merged

solidity style guide 2.0 #10995

merged 3 commits into from
Nov 10, 2023

Conversation

RensR
Copy link
Contributor

@RensR RensR commented Oct 18, 2023

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.

@github-actions
Copy link
Contributor

I see that you haven't updated any CHANGELOG files. Would it make sense to do so?

@RensR RensR marked this pull request as ready for review October 18, 2023 14:38
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.
Copy link
Collaborator

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."

Copy link
Contributor Author

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


### Comments

- Comments should be in the `//` (default) or `///` (natspec) format, not the `/* */` format.
Copy link
Collaborator

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.

Copy link
Contributor Author

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

Comment on lines 41 to 45
import {IInterface} from "../interfaces/IInterface.sol";

import {AnythingElse} from "../code/AnythingElse.sol";

import {ThirdPartyCode} from "../../vendor/ThirdPartyCode.sol";
Copy link
Collaborator

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?

Copy link
Contributor Author

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


- 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.
Copy link
Collaborator

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.

Copy link
Contributor Author

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?

Copy link
Contributor

@yosriady yosriady left a 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
// ================================================================
Copy link
Contributor

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?

Copy link
Contributor Author

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

- Imports should follow the following format:

```solidity
import {IInterface} from "../interfaces/IInterface.sol";
Copy link
Contributor

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?

Copy link
Contributor Author

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

# 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.
Copy link
Collaborator

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?

Copy link
Contributor Author

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

contracts/STYLE_GUIDE.md Outdated Show resolved Hide resolved
@austinborn
Copy link
Collaborator

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?


### 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.
Copy link
Contributor

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)

Copy link
Contributor Author

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.

Comment on lines 322 to 327
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";
Copy link
Contributor

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?

Copy link
Contributor Author

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


### 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.
Copy link

@Oozyx Oozyx Oct 25, 2023

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.

Copy link
Contributor Author

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.
Copy link

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?

Copy link
Contributor Author

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

@RensR
Copy link
Contributor Author

RensR commented Oct 27, 2023

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?

I think that isn't style related and very much dependent on what is being built and how.

@cl-sonarqube-production
Copy link

SonarQube Quality Gate

Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

No Coverage information No Coverage information
No Duplication information No Duplication information

@RensR RensR added this pull request to the merge queue Nov 10, 2023
Merged via the queue into develop with commit 0918b37 Nov 10, 2023
84 checks passed
@RensR RensR deleted the solidity-style-guide-2.0 branch November 10, 2023 15:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants