Skip to content

Commit 5088059

Browse files
committed
Nits on wording
1 parent df88488 commit 5088059

File tree

1 file changed

+87
-90
lines changed

1 file changed

+87
-90
lines changed

README.md

Lines changed: 87 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -16,84 +16,83 @@ We should be as specific and thorough as possible when defining our style, testi
1616

1717
#### 1. Names of internal functions in a library should not have an underscore prefix.
1818

19-
The style guide states
20-
21-
> Underscore Prefix for Non-external Functions and Variables
22-
23-
One of the motivations for this rule is that it is a helpful visual clue.
19+
YES:
2420

25-
> Leading underscores allow you to immediately recognize the intent of such functions...
21+
```solidity
22+
Library.function()
23+
```
2624

27-
We agree that a leading underscore is a useful visual clue, and this is why we oppose using them for internal library functions that can be called from other contracts. Visually, it looks wrong.
25+
NO:
2826

2927
```solidity
3028
Library._function()
3129
```
3230

33-
or
31+
If a function should never be called from another contract, it should be marked private and its name should have a leading underscore.
32+
33+
#### Justification
34+
The style guide states `Underscore Prefix for Non-external Functions and Variables`. One motivation for this rule is providing a helpful visual clue:
35+
36+
> Leading underscores allow you to immediately recognize the intent of such functions...
37+
38+
We agree that a leading underscore is a useful visual clue, and this is why we oppose using them for internal library functions that can be called from other contracts. Visually, it looks wrong.
3439

3540
```solidity
3641
using Library for bytes
3742
bytes._function()
3843
```
3944

40-
Note, we cannot remedy this by insisting on the use public functions. Whether a library functions are internal or external has important implications. From the [Solidity documentation](https://docs.soliditylang.org/en/latest/contracts.html#libraries)
41-
42-
> ... the code of internal library functions that are called from a contract and all functions called from therein will at compile time be included in the calling contract, and a regular JUMP call will be used instead of a DELEGATECALL.
45+
We could remedy this by insisting on the use public functions. However, developers may prefer internal functions because they are more gas efficient to call, due to how libraries are compiled in Solidity:
4346

44-
Developers may prefer internal functions because they are more gas efficient to call.
45-
46-
If a function should never be called from another contract, it should be marked private and its name should have a leading underscore.
47+
> ... the code of internal library functions that are called from a contract and all functions called from therein will at compile time be included in the calling contract, and a regular JUMP call will be used instead of a DELEGATECALL. ([source](https://docs.soliditylang.org/en/latest/contracts.html#libraries))
4748
4849
### C. Additions
4950

50-
#### 1. Prefer custom errors.
51+
#### 1. Use custom errors.
5152

52-
Custom errors are in some cases more gas efficient and allow passing useful information.
53+
Custom errors should be used wherever possible (an exception could be introducing inconsistencies to existing code bases) - they allow for passing of useful information and are in some cases more gas efficient.
5354

5455
#### 2. Custom error names should be CapWords style.
5556

5657
For example, `InsufficientBalance`.
5758

58-
#### 3. Events
59-
60-
##### A. Events names should be past tense.
59+
#### 3. Events names should be past tense.
6160

6261
Events should track things that _happened_ and so should be past tense. Using past tense also helps avoid naming collisions with structs or functions.
6362

6463
We are aware this does not follow precedent from early ERCs, like [ERC-20](https://eips.ethereum.org/EIPS/eip-20). However it does align with some more recent high profile Solidity, e.g. [1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/976a3d53624849ecaef1231019d2052a16a39ce4/contracts/access/Ownable.sol#L33), [2](https://github.com/aave/aave-v3-core/blob/724a9ef43adf139437ba87dcbab63462394d4601/contracts/interfaces/IAaveOracle.sol#L25-L31), [3](https://github.com/ProjectOpenSea/seaport/blob/1d12e33b71b6988cbbe955373ddbc40a87bd5b16/contracts/zones/interfaces/PausableZoneEventsAndErrors.sol#L25-L41).
6564

65+
YES:
66+
67+
```solidity
68+
event OwnerUpdated(address newOwner);
69+
```
70+
6671
NO:
6772

6873
```solidity
6974
event OwnerUpdate(address newOwner);
7075
```
7176

77+
#### 4. Event names should have `SubjectVerb` naming format.
78+
7279
YES:
7380

7481
```solidity
7582
event OwnerUpdated(address newOwner);
7683
```
7784

78-
##### B. Prefer `SubjectVerb` naming format.
79-
8085
NO:
8186

8287
```solidity
8388
event UpdatedOwner(address newOwner);
8489
```
8590

86-
YES:
87-
88-
```solidity
89-
event OwnerUpdated(address newOwner);
90-
```
91-
92-
#### 4. Avoid using assembly.
91+
#### 5. Avoid using assembly.
9392

9493
Assembly code is hard to read and audit. We should avoid it unless the gas savings are very consequential, e.g. > 25%.
9594

96-
#### 5. Avoid unnecessary named return arguments.
95+
#### 6. Avoid unnecessary named return arguments.
9796

9897
In short functions, named return arguments are unnecessary.
9998

@@ -113,93 +112,91 @@ function validate(UserOperation calldata userOp) external returns (bytes memory
113112

114113
However, it is important to be explicit when returning early.
115114

116-
NO:
115+
YES:
117116

118117
```solidity
119118
function validate(UserOperation calldata userOp) external returns (bytes memory context, uint256 validationData) {
120119
context = "";
121120
validationData = 1;
122121
123122
if (condition) {
124-
return;
123+
return (context, validationData);
125124
}
126125
}
127126
```
128127

129-
YES:
128+
NO:
130129

131130
```solidity
132131
function validate(UserOperation calldata userOp) external returns (bytes memory context, uint256 validationData) {
133132
context = "";
134133
validationData = 1;
135134
136135
if (condition) {
137-
return (context, validationData);
136+
return;
138137
}
139138
}
140139
```
141140

142-
#### 6. Prefer composition over inheritance.
141+
#### 7. Prefer composition over inheritance.
143142

144143
If a function or set of functions could reasonably be defined as its own contract or as a part of a larger contract, prefer defining it as part of a larger contract. This makes the code easier to understand and audit.
145144

146145
Note this _does not_ mean that we should avoid inheritance, in general. Inheritance is useful at times, most especially when building on existing, trusted contracts. For example, _do not_ reimplement `Ownable` functionality to avoid inheritance. Inherit `Ownable` from a trusted vendor, such as [OpenZeppelin](https://github.com/OpenZeppelin/openzeppelin-contracts/) or [Solady](https://github.com/Vectorized/solady).
147146

148-
#### 7. Avoid writing interfaces.
147+
#### 8. Avoid writing interfaces.
149148

150149
Interfaces separate NatSpec from contract logic, requiring readers to do more work to understand the code. For this reason, they should be avoided.
151150

152-
#### 8. Avoid unnecessary version Pragma constraints.
151+
#### 9. Avoid unnecessary version Pragma constraints.
153152

154-
While the main contracts we deploy should specify a single Solidity version, all supporting contracts and libraries should have as open a Pragma as possible. A good rule of thumb is to the next major version. For example
153+
While the main contracts we deploy should specify a single Solidity version, all supporting contracts and libraries should have as open a Pragma as possible. A good rule of thumb is to use the next major version. For example
155154

156155
```solidity
157156
pragma solidity ^0.8.0;
158157
```
159158

160-
#### 9. Struct and Error Definitions
161-
162-
##### A. Prefer declaring structs and errors within the interface, contract, or library where they are used.
159+
#### 10. Default to declaring structs and errors within the interface, contract, or library where they are used.
163160

164-
##### B. If a struct or error is used across many files, with no interface, contract, or library reasonably being the "owner," then define them in their own file. Multiple structs and errors can be defined together in one file.
161+
This helps with clarity.
165162

166-
#### 10. Imports
163+
However, if a struct or error is used across many files, with no interface, contract, or library reasonably being the "owner," then define them in their own file. Multiple structs and errors can be defined together in one file.
167164

168-
##### A. Use named imports.
165+
#### 11. Use named imports.
169166

170167
Named imports help readers understand what exactly is being used and where it is originally declared.
171168

172-
NO:
169+
YES:
173170

174171
```solidity
175-
import "./contract.sol"
172+
import {Contract} from "./contract.sol"
176173
```
177174

178-
YES:
175+
NO:
179176

180177
```solidity
181-
import {Contract} from "./contract.sol"
178+
import "./contract.sol"
182179
```
183180

184181
For convenience, named imports do not have to be used in test files.
185182

186-
##### B. Order imports alphabetically (A to Z) by file name.
183+
#### 12. Order imports alphabetically by file name.
187184

188-
NO:
185+
YES:
189186

190187
```solidity
191-
import {B} from './B.sol'
192188
import {A} from './A.sol'
189+
import {B} from './B.sol'
193190
```
194191

195-
YES:
192+
NO:
196193

197194
```solidity
198-
import {A} from './A.sol'
199195
import {B} from './B.sol'
196+
import {A} from './A.sol'
200197
```
201198

202-
##### C. Group imports by external and local with a new line in between.
199+
#### 13. Group imports by external and local with a new line in between.
203200

204201
For example
205202

@@ -219,7 +216,7 @@ import {MyHelper} from '../src/MyHelper.sol'
219216
import {Mock} from './mocks/Mock.sol'
220217
```
221218

222-
#### 11. Commenting to group sections of the code is permitted.
219+
#### 14. Commenting to group sections of the code is permitted.
223220

224221
Sometimes authors and readers find it helpful to comment dividers between groups of functions. This permitted, however ensure the style guide [ordering of functions](https://docs.soliditylang.org/en/latest/style-guide.html#order-of-functions) is still followed.
225222

@@ -235,40 +232,40 @@ For example
235232
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
236233
```
237234

238-
#### 12. ASCII Art
235+
#### 15. ASCII Art
239236

240237
ASCII art is permitted in the space between the end of the Pragmas and the beginning of the imports.
241238

242-
#### 13. Prefer named arguments.
239+
#### 16. Prefer named arguments.
243240

244241
Passing arguments to functions, events, and errors with explicit naming is helpful for clarity, especially when the name of the variable passed does not match the parameter name.
245242

246-
NO:
243+
YES:
247244

248245
```
249-
pow(x, y, v)
246+
pow({base: x, exponent: y, scalar: v})
250247
```
251248

252-
YES:
249+
NO:
253250

254251
```
255-
pow({base: x, exponent: y, scalar: v})
252+
pow(x, y, v)
256253
```
257254

258-
#### 14. Prefer named parameters in mapping types.
255+
#### 17. Prefer named parameters in mapping types.
259256

260257
Explicit naming parameters in mapping types is helpful for clarity, especially when nesting is used.
261258

262-
NO:
259+
YES:
263260

264261
```
265-
mapping(uint256 => mapping(address => uint256)) public balances;
262+
mapping(address account => mapping(address asset => uint256 amount)) public balances;
266263
```
267264

268-
YES:
265+
NO:
269266

270267
```
271-
mapping(address account => mapping(address asset => uint256 amount)) public balances;
268+
mapping(uint256 => mapping(address => uint256)) public balances;
272269
```
273270

274271
## 2. Development
@@ -304,21 +301,10 @@ contract TransferFromTest {
304301
}
305302
```
306303

307-
#### 4. Prefer tests that test one thing.
304+
#### 4. Write tests that test one thing.
308305

309306
This is generally good practice, but especially so because Forge does not give line numbers on assertion failures. This makes it hard to track down what, exactly, failed if a test has many assertions.
310307

311-
NO:
312-
313-
```solidity
314-
function test_transferFrom_works() {
315-
// debits correctly
316-
// credits correctly
317-
// emits correctly
318-
// reverts correctly
319-
}
320-
```
321-
322308
YES:
323309

324310
```solidity
@@ -339,6 +325,17 @@ function test_transferFrom_reverts_whenAmountExceedsBalance() {
339325
}
340326
```
341327

328+
NO:
329+
330+
```solidity
331+
function test_transferFrom_works() {
332+
// debits correctly
333+
// credits correctly
334+
// emits correctly
335+
// reverts correctly
336+
}
337+
```
338+
342339
Note, this does not mean a test should only ever have one assertion. Sometimes having multiple assertions is helpful for certainty on what is being tested.
343340

344341
```solidity
@@ -351,47 +348,47 @@ function test_transferFrom_creditsTo() {
351348

352349
#### 5. Use variables for important values in tests
353350

354-
NO:
351+
YES:
355352

356353
```solidity
357354
function test_transferFrom_creditsTo() {
358355
assertEq(balanceOf(to), 0);
359-
transferFrom(from, to, 10);
360-
assertEq(balanceOf(to), 10);
356+
uint amount = 10;
357+
transferFrom(from, to, amount);
358+
assertEq(balanceOf(to), amount);
361359
}
362360
```
363361

364-
YES:
362+
NO:
365363

366364
```solidity
367365
function test_transferFrom_creditsTo() {
368366
assertEq(balanceOf(to), 0);
369-
uint amount = 10;
370-
transferFrom(from, to, amount);
371-
assertEq(balanceOf(to), amount);
367+
transferFrom(from, to, 10);
368+
assertEq(balanceOf(to), 10);
372369
}
373370
```
374371

375372
#### 6. Prefer fuzz tests.
376373

377374
All else being equal, prefer fuzz tests.
378375

379-
NO:
376+
YES:
380377

381378
```solidity
382-
function test_transferFrom_creditsTo() {
379+
function test_transferFrom_creditsTo(uint amount) {
383380
assertEq(balanceOf(to), 0);
384-
uint amount = 10;
385381
transferFrom(from, to, amount);
386382
assertEq(balanceOf(to), amount);
387383
}
388384
```
389385

390-
YES:
386+
NO:
391387

392388
```solidity
393-
function test_transferFrom_creditsTo(uint amount) {
389+
function test_transferFrom_creditsTo() {
394390
assertEq(balanceOf(to), 0);
391+
uint amount = 10;
395392
transferFrom(from, to, amount);
396393
assertEq(balanceOf(to), amount);
397394
}
@@ -412,7 +409,7 @@ We should avoid adding to these or defining any remappings explicitly, as it mak
412409

413410
### D. Upgradability
414411

415-
#### 1. Prefer [ERC-7201](https://eips.ethereum.org/EIPS/eip-7201) "Namespaced Storage Layout" convention to avoid storage collisions.
412+
#### 1. Use [ERC-7201](https://eips.ethereum.org/EIPS/eip-7201) "Namespaced Storage Layout" convention to avoid storage collisions.
416413

417414
### E. Structs
418415

0 commit comments

Comments
 (0)