You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+87-90Lines changed: 87 additions & 90 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -16,84 +16,83 @@ We should be as specific and thorough as possible when defining our style, testi
16
16
17
17
#### 1. Names of internal functions in a library should not have an underscore prefix.
18
18
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:
24
20
25
-
> Leading underscores allow you to immediately recognize the intent of such functions...
21
+
```solidity
22
+
Library.function()
23
+
```
26
24
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:
28
26
29
27
```solidity
30
28
Library._function()
31
29
```
32
30
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.
34
39
35
40
```solidity
36
41
using Library for bytes
37
42
bytes._function()
38
43
```
39
44
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:
43
46
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))
47
48
48
49
### C. Additions
49
50
50
-
#### 1. Prefer custom errors.
51
+
#### 1. Use custom errors.
51
52
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.
53
54
54
55
#### 2. Custom error names should be CapWords style.
55
56
56
57
For example, `InsufficientBalance`.
57
58
58
-
#### 3. Events
59
-
60
-
##### A. Events names should be past tense.
59
+
#### 3. Events names should be past tense.
61
60
62
61
Events should track things that _happened_ and so should be past tense. Using past tense also helps avoid naming collisions with structs or functions.
63
62
64
63
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).
65
64
65
+
YES:
66
+
67
+
```solidity
68
+
event OwnerUpdated(address newOwner);
69
+
```
70
+
66
71
NO:
67
72
68
73
```solidity
69
74
event OwnerUpdate(address newOwner);
70
75
```
71
76
77
+
#### 4. Event names should have `SubjectVerb` naming format.
78
+
72
79
YES:
73
80
74
81
```solidity
75
82
event OwnerUpdated(address newOwner);
76
83
```
77
84
78
-
##### B. Prefer `SubjectVerb` naming format.
79
-
80
85
NO:
81
86
82
87
```solidity
83
88
event UpdatedOwner(address newOwner);
84
89
```
85
90
86
-
YES:
87
-
88
-
```solidity
89
-
event OwnerUpdated(address newOwner);
90
-
```
91
-
92
-
#### 4. Avoid using assembly.
91
+
#### 5. Avoid using assembly.
93
92
94
93
Assembly code is hard to read and audit. We should avoid it unless the gas savings are very consequential, e.g. > 25%.
95
94
96
-
#### 5. Avoid unnecessary named return arguments.
95
+
#### 6. Avoid unnecessary named return arguments.
97
96
98
97
In short functions, named return arguments are unnecessary.
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.
145
144
146
145
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).
147
146
148
-
#### 7. Avoid writing interfaces.
147
+
#### 8. Avoid writing interfaces.
149
148
150
149
Interfaces separate NatSpec from contract logic, requiring readers to do more work to understand the code. For this reason, they should be avoided.
151
150
152
-
#### 8. Avoid unnecessary version Pragma constraints.
151
+
#### 9. Avoid unnecessary version Pragma constraints.
153
152
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
155
154
156
155
```solidity
157
156
pragma solidity ^0.8.0;
158
157
```
159
158
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.
163
160
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.
165
162
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.
167
164
168
-
##### A. Use named imports.
165
+
####11. Use named imports.
169
166
170
167
Named imports help readers understand what exactly is being used and where it is originally declared.
171
168
172
-
NO:
169
+
YES:
173
170
174
171
```solidity
175
-
import "./contract.sol"
172
+
import {Contract} from "./contract.sol"
176
173
```
177
174
178
-
YES:
175
+
NO:
179
176
180
177
```solidity
181
-
import {Contract} from "./contract.sol"
178
+
import "./contract.sol"
182
179
```
183
180
184
181
For convenience, named imports do not have to be used in test files.
185
182
186
-
##### B. Order imports alphabetically (A to Z) by file name.
183
+
####12. Order imports alphabetically by file name.
187
184
188
-
NO:
185
+
YES:
189
186
190
187
```solidity
191
-
import {B} from './B.sol'
192
188
import {A} from './A.sol'
189
+
import {B} from './B.sol'
193
190
```
194
191
195
-
YES:
192
+
NO:
196
193
197
194
```solidity
198
-
import {A} from './A.sol'
199
195
import {B} from './B.sol'
196
+
import {A} from './A.sol'
200
197
```
201
198
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.
203
200
204
201
For example
205
202
@@ -219,7 +216,7 @@ import {MyHelper} from '../src/MyHelper.sol'
219
216
import {Mock} from './mocks/Mock.sol'
220
217
```
221
218
222
-
#### 11. Commenting to group sections of the code is permitted.
219
+
#### 14. Commenting to group sections of the code is permitted.
223
220
224
221
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.
ASCII art is permitted in the space between the end of the Pragmas and the beginning of the imports.
241
238
242
-
#### 13. Prefer named arguments.
239
+
#### 16. Prefer named arguments.
243
240
244
241
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.
245
242
246
-
NO:
243
+
YES:
247
244
248
245
```
249
-
pow(x, y, v)
246
+
pow({base: x, exponent: y, scalar: v})
250
247
```
251
248
252
-
YES:
249
+
NO:
253
250
254
251
```
255
-
pow({base: x, exponent: y, scalar: v})
252
+
pow(x, y, v)
256
253
```
257
254
258
-
#### 14. Prefer named parameters in mapping types.
255
+
#### 17. Prefer named parameters in mapping types.
259
256
260
257
Explicit naming parameters in mapping types is helpful for clarity, especially when nesting is used.
261
258
262
-
NO:
259
+
YES:
263
260
264
261
```
265
-
mapping(uint256 => mapping(address => uint256)) public balances;
262
+
mapping(address account => mapping(address asset => uint256 amount)) public balances;
266
263
```
267
264
268
-
YES:
265
+
NO:
269
266
270
267
```
271
-
mapping(address account => mapping(address asset => uint256 amount)) public balances;
268
+
mapping(uint256 => mapping(address => uint256)) public balances;
272
269
```
273
270
274
271
## 2. Development
@@ -304,21 +301,10 @@ contract TransferFromTest {
304
301
}
305
302
```
306
303
307
-
#### 4. Prefer tests that test one thing.
304
+
#### 4. Write tests that test one thing.
308
305
309
306
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.
310
307
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
-
322
308
YES:
323
309
324
310
```solidity
@@ -339,6 +325,17 @@ function test_transferFrom_reverts_whenAmountExceedsBalance() {
339
325
}
340
326
```
341
327
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
+
342
339
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.
343
340
344
341
```solidity
@@ -351,47 +348,47 @@ function test_transferFrom_creditsTo() {
351
348
352
349
#### 5. Use variables for important values in tests
353
350
354
-
NO:
351
+
YES:
355
352
356
353
```solidity
357
354
function test_transferFrom_creditsTo() {
358
355
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);
361
359
}
362
360
```
363
361
364
-
YES:
362
+
NO:
365
363
366
364
```solidity
367
365
function test_transferFrom_creditsTo() {
368
366
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);
372
369
}
373
370
```
374
371
375
372
#### 6. Prefer fuzz tests.
376
373
377
374
All else being equal, prefer fuzz tests.
378
375
379
-
NO:
376
+
YES:
380
377
381
378
```solidity
382
-
function test_transferFrom_creditsTo() {
379
+
function test_transferFrom_creditsTo(uint amount) {
383
380
assertEq(balanceOf(to), 0);
384
-
uint amount = 10;
385
381
transferFrom(from, to, amount);
386
382
assertEq(balanceOf(to), amount);
387
383
}
388
384
```
389
385
390
-
YES:
386
+
NO:
391
387
392
388
```solidity
393
-
function test_transferFrom_creditsTo(uint amount) {
389
+
function test_transferFrom_creditsTo() {
394
390
assertEq(balanceOf(to), 0);
391
+
uint amount = 10;
395
392
transferFrom(from, to, amount);
396
393
assertEq(balanceOf(to), amount);
397
394
}
@@ -412,7 +409,7 @@ We should avoid adding to these or defining any remappings explicitly, as it mak
0 commit comments