├── .gitignore ├── bun.lockb ├── dprint.json ├── .github └── workflows │ └── fmt.yml ├── package.json ├── README-pt-br.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coinbase/solidity-style-guide/HEAD/bun.lockb -------------------------------------------------------------------------------- /dprint.json: -------------------------------------------------------------------------------- 1 | { 2 | "markdown": { 3 | }, 4 | "excludes": [], 5 | "plugins": [ 6 | "https://plugins.dprint.dev/markdown-0.16.4.wasm" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.github/workflows/fmt.yml: -------------------------------------------------------------------------------- 1 | name: format check 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | style: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: dprint/check@v2.2 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { "dprint": "^0.45.0", "simple-git-hooks": "^2.11.0" }, 3 | "scripts": { 4 | "format": "dprint fmt", 5 | "format-check": "dprint check", 6 | "prepare": "bun x simple-git-hooks" 7 | }, 8 | "simple-git-hooks": { 9 | "pre-commit": "bun run format-check" 10 | } 11 | } -------------------------------------------------------------------------------- /README-pt-br.md: -------------------------------------------------------------------------------- 1 | # Guia de Estilo Solidity da Coinbase 2 | 3 | Este é um guia para os engenheiros da Coinbase que desenvolvem contratos inteligentes baseados em EVM. Utilizamos Solidity para desenvolver tais contratos, portanto, chamamos isso de "Guia de Estilo Solidity." Este guia também abrange práticas de desenvolvimento e teste. Estamos compartilhando isso publicamente caso seja útil para outros. 4 | 5 | ## Por quê? 6 | 7 | Devemos ser extremamente específicos e minuciosos ao definir nosso estilo, testes e práticas de desenvolvimento. O tempo que economizamos ao não precisar debater essas questões em pull requests é tempo produtivo que pode ser investido em outras discussões e revisões. Seguir o guia de estilo é uma prova de cuidado. 8 | 9 | ![tom-sachs-gq-style-spring-2019-05](https://github.com/coinbase/solidity-style-guide/assets/6678357/9e904107-e83f-4d89-a405-d3f1394d8de4) 10 | 11 | ## 1. Estilo 12 | 13 | ### A. A menos que uma exceção ou adição seja especificamente notada, seguimos o [Guia de Estilo Solidity](https://docs.soliditylang.org/en/latest/style-guide.html). 14 | 15 | ### B. Exceções 16 | 17 | #### 1. Nomes de funções internas em uma biblioteca não devem ter um prefixo de sublinhado. 18 | 19 | O guia de estilo afirma: 20 | 21 | > Prefixo de sublinhado para funções e variáveis não externas 22 | 23 | Uma das motivações para esta regra é que ela é uma pista visual útil. 24 | 25 | > Sublinhados iniciais permitem que você reconheça imediatamente a intenção de tais funções... 26 | 27 | Concordamos que um sublinhado inicial é uma pista visual útil, e é por isso que nos opomos ao uso deles para funções internas de biblioteca que podem ser chamadas de outros contratos. Visualmente, parece errado. 28 | 29 | ```solidity 30 | Library._function() 31 | ``` 32 | 33 | ou 34 | 35 | ```solidity 36 | using Library for bytes 37 | bytes._function() 38 | ``` 39 | 40 | Observe, não podemos remediar isso insistindo no uso de funções públicas. Se as funções de uma biblioteca são internas ou externas tem implicações importantes. Na [documentação do Solidity] 41 | (https://docs.soliditylang.org/en/latest/contracts.html#libraries): 42 | 43 | ``` 44 | ... o código das funções internas de uma biblioteca que são chamadas de um contrato e todas as funções chamadas a partir daí serão incluídas no contrato que faz a chamada no momento da compilação, e uma chamada JUMP regular será usada em vez de uma DELEGATECALL. 45 | ``` 46 | 47 | Os desenvolvedores podem preferir funções internas porque são mais eficientes em termos de gás para chamadas. 48 | 49 | Se uma função nunca deve ser chamada de outro contrato, ela deve ser marcada como privada e seu nome deve ter um sublinhado inicial. 50 | 51 | ### C. Adições 52 | 53 | #### 1. Prefira erros personalizados. 54 | 55 | Erros personalizados são, em alguns casos, mais eficientes em termos de gás e permitem a passagem de informações úteis. 56 | 57 | #### 2. Nomes de erros personalizados devem seguir o estilo CapWords. 58 | 59 | Por exemplo, `InsufficientBalance`. 60 | 61 | #### 3. Nomes de eventos devem ser no passado. 62 | 63 | Por exemplo, `UpdatedOwner` não `UpdateOwner`. 64 | 65 | Eventos devem rastrear coisas que _aconteceram_ e, portanto, devem ser no passado. Usar o passado também ajuda a evitar colisões de nomes com estruturas ou funções. 66 | 67 | Estamos cientes de que isso não segue o precedente dos primeiros ERCs, como o [ERC-20](https://eips.ethereum.org/EIPS/eip-20). No entanto, isso se alinha com algumas implementações mais recentes de Solidity de alto perfil, exemplos: [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). 68 | 69 | #### 4. Evite usar assembly. 70 | 71 | O código assembly é difícil de ler e auditar. Devemos evitá-lo, a menos que as economias de gás sejam muito significativas, por exemplo, > 25%. 72 | 73 | #### 5. Evite argumentos de retorno nomeados desnecessários. 74 | 75 | Em funções curtas, argumentos de retorno nomeados são desnecessários. 76 | 77 | NÃO: 78 | 79 | ```solidity 80 | function add(uint a, uint b) public returns (uint result) { 81 | result = a + b; 82 | } 83 | ``` 84 | 85 | Argumentos de retorno nomeados podem ser úteis em funções com múltiplos valores retornados. 86 | 87 | ```solidity 88 | function validate(UserOperation calldata userOp) external returns (bytes memory context, uint256 validationData) 89 | ``` 90 | 91 | No entanto, é importante ser explícito ao retornar mais cedo. 92 | 93 | NÃO: 94 | 95 | ```solidity 96 | function validate(UserOperation calldata userOp) external returns (bytes memory context, uint256 validationData) { 97 | context = ""; 98 | validationData = 1; 99 | 100 | if (condition) { 101 | return; 102 | } 103 | } 104 | ``` 105 | 106 | SIM: 107 | 108 | ```solidity 109 | function validate(UserOperation calldata userOp) external returns (bytes memory context, uint256 validationData) { 110 | context = ""; 111 | validationData = 1; 112 | 113 | if (condition) { 114 | 115 | 116 | return (context, validationData); 117 | } 118 | } 119 | ``` 120 | 121 | #### 6. Prefira composição a herança. 122 | 123 | Se uma função ou conjunto de funções poderia razoavelmente ser definido como seu próprio contrato ou como parte de um contrato maior, prefira definir como parte de um contrato maior. Isso torna o código mais fácil de entender e auditar. 124 | 125 | Observe que isso _não_ significa que devemos evitar a herança, em geral. A herança é útil às vezes, especialmente quando se constrói em contratos existentes e confiáveis. Por exemplo, _não_ reimplemente a funcionalidade `Ownable` para evitar a herança. Herde `Ownable` de um fornecedor confiável, como [OpenZeppelin](https://github.com/OpenZeppelin/openzeppelin-contracts/) ou [Solady](https://github.com/Vectorized/solady). 126 | 127 | #### 7. Evite escrever interfaces. 128 | 129 | Interfaces separam NatSpec da lógica do contrato, exigindo que os leitores façam mais trabalho para entender o código. Por essa razão, elas devem ser evitadas. 130 | 131 | #### 8. Evite restrições desnecessárias de versão Pragma. 132 | 133 | Embora os contratos principais que implantamos devem especificar uma única versão do Solidity, todos os contratos de suporte e bibliotecas devem ter um Pragma tão aberto quanto possível. Uma boa regra é até a próxima versão principal. Por exemplo: 134 | 135 | ```solidity 136 | pragma solidity ^0.8.0; 137 | ``` 138 | 139 | #### 9. Definições de Struct e Erro 140 | 141 | ##### A. Prefira declarar structs e erros dentro da interface, contrato ou biblioteca onde são usados. 142 | 143 | ##### B. Se um struct ou erro é usado em muitos arquivos, sem nenhuma interface, contrato ou biblioteca sendo razoavelmente o "dono," então defina-os em seu próprio arquivo. Vários structs e erros podem ser definidos juntos em um arquivo. 144 | 145 | #### 10. Importações 146 | 147 | ##### A. Use importações nomeadas. 148 | 149 | Importações nomeadas ajudam os leitores a entender o que exatamente está sendo usado e onde é originalmente declarado. 150 | 151 | NÃO: 152 | 153 | ```solidity 154 | import "./contract.sol" 155 | ``` 156 | 157 | SIM: 158 | 159 | ```solidity 160 | import {Contract} from "./contract.sol" 161 | ``` 162 | 163 | Para conveniência, importações nomeadas não precisam ser usadas em arquivos de teste. 164 | 165 | ##### B. Ordene importações alfabeticamente (de A a Z) pelo nome do arquivo. 166 | 167 | NÃO: 168 | 169 | ```solidity 170 | import {B} from './B.sol' 171 | import {A} from './A.sol' 172 | ``` 173 | 174 | SIM: 175 | 176 | ```solidity 177 | import {A} from './A.sol' 178 | import {B} from './B.sol' 179 | ``` 180 | 181 | ##### C. Agrupe importações por externas e locais com uma nova linha entre elas. 182 | 183 | Por exemplo: 184 | 185 | ```solidity 186 | import {Math} from '/solady/Math.sol' 187 | 188 | import {MyHelper} from './MyHelper.sol' 189 | ``` 190 | 191 | Em arquivos de teste, importações de `/test` devem ser seu próprio grupo também. 192 | 193 | ```solidity 194 | import {Math} from '/solady/Math.sol' 195 | 196 | import {MyHelper} from '../src/MyHelper.sol' 197 | 198 | import {Mock} from './mocks/Mock.sol' 199 | ``` 200 | 201 | #### 11. Comentar para agrupar seções do código é permitido. 202 | 203 | Às vezes, autores e leitores acham útil comentar divisórias entre grupos de funções. Isso é permitido, no entanto, garanta que o guia de estilo [ordenação de funções](https://docs.soliditylang.org/en/latest/style-guide.html#order-of-functions) ainda seja seguido. 204 | 205 | Por exemplo: 206 | 207 | ```solidity 208 | /// External Functions /// 209 | ``` 210 | 211 | ```solidity 212 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ 213 | /* VALIDATION OPERATIONS */ 214 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ 215 | ``` 216 | 217 | #### 12. Arte ASCII 218 | 219 | Arte ASCII é permitida no espaço entre o fim do Pragma e o início das importações. 220 | 221 | #### 13. Prefira argumentos nomeados. 222 | 223 | Passar argumentos para funções, eventos e erros com nomeação explícita ajuda na clareza 224 | 225 | , especialmente quando o nome da variável passada não corresponde ao nome do parâmetro. 226 | 227 | NÃO: 228 | 229 | ``` 230 | pow(x, y, v) 231 | ``` 232 | 233 | SIM: 234 | 235 | ``` 236 | pow({base: x, exponent: y, scalar: v}) 237 | ``` 238 | 239 | ## 2. Desenvolvimento 240 | 241 | ### A. Uso do [Forge](https://github.com/foundry-rs/foundry/tree/master/crates/forge) para teste e gerenciamento de dependências. 242 | 243 | ### B. Testes 244 | 245 | #### 1. Nomes de arquivos de teste devem seguir as convenções do Guia de Estilo Solidity para nomes de arquivos e também ter `.t` antes de `.sol`. 246 | 247 | Por exemplo, `ERC20.t.sol` 248 | 249 | #### 2. Nomes de contratos de teste devem incluir o nome do contrato ou função sendo testado, seguido por "Test". 250 | 251 | Por exemplo, 252 | 253 | - `ERC20Test` 254 | - `TransferFromTest` 255 | 256 | #### 3. Nomes de teste devem seguir a convenção `test_functionName_outcome_optionalContext` 257 | 258 | Por exemplo 259 | 260 | - `test_transferFrom_debitsFromAccountBalance` 261 | - `test_transferFrom_debitsFromAccountBalance_whenCalledViaPermit` 262 | - `test_transferFrom_reverts_whenAmountExceedsBalance` 263 | 264 | Se o contrato é nomeado após uma função, então o nome da função pode ser omitido. 265 | 266 | ```solidity 267 | contract TransferFromTest { 268 | function test_debitsFromAccountBalance() ... 269 | } 270 | ``` 271 | 272 | #### 4. Prefira testes que testem uma coisa. 273 | 274 | Isso é geralmente uma boa prática, mas especialmente porque Forge não fornece números de linha em falhas de afirmação. Isso dificulta rastrear o que, exatamente, falhou se um teste tem muitas afirmações. 275 | 276 | NÃO: 277 | 278 | ```solidity 279 | function test_transferFrom_works() { 280 | // debita corretamente 281 | // credita corretamente 282 | // emite corretamente 283 | // reverte corretamente 284 | } 285 | ``` 286 | 287 | SIM: 288 | 289 | ```solidity 290 | function test_transferFrom_debitsFrom() { 291 | ... 292 | } 293 | 294 | function test_transferFrom_creditsTo() { 295 | ... 296 | } 297 | 298 | function test_transferFrom_emitsCorrectly() { 299 | ... 300 | } 301 | 302 | function test_transferFrom_reverts_whenAmountExceedsBalance() { 303 | ... 304 | } 305 | ``` 306 | 307 | Observe, isso não significa que um teste deve ter apenas uma afirmação. Às vezes, ter várias afirmações é útil para certeza sobre o que está sendo testado. 308 | 309 | ```solidity 310 | function test_transferFrom_creditsTo() { 311 | assertEq(balanceOf(to), 0); 312 | ... 313 | assertEq(balanceOf(to), amount); 314 | } 315 | ``` 316 | 317 | #### 5. Use variáveis para valores importantes em testes 318 | 319 | NÃO: 320 | 321 | ```solidity 322 | function test_transferFrom_creditsTo() { 323 | assertEq(balanceOf(to), 0); 324 | transferFrom(from, to, 10); 325 | assertEq(balanceOf(to), 10); 326 | } 327 | ``` 328 | 329 | SIM: 330 | 331 | ```solidity 332 | function test_transferFrom_creditsTo() { 333 | assertEq(balanceOf(to), 0); 334 | uint amount = 10; 335 | transferFrom(from, to, amount); 336 | assertEq(balanceOf(to), amount); 337 | } 338 | ``` 339 | 340 | #### 6. Prefira testes de fuzz. 341 | 342 | Tudo o mais sendo igual, prefira testes de fuzz. 343 | 344 | NÃO: 345 | 346 | ```solidity 347 | function test_transferFrom_creditsTo() { 348 | assertEq(balanceOf(to), 0); 349 | uint amount = 10; 350 | transferFrom(from, to, amount); 351 | assertEq(balanceOf(to), amount); 352 | } 353 | ``` 354 | 355 | SIM: 356 | 357 | ```solidity 358 | function test_transferFrom_creditsTo(uint amount) { 359 | assertEq(balanceOf(to), 0); 360 | transferFrom(from, to, amount); 361 | assertEq(balanceOf(to), amount); 362 | } 363 | ``` 364 | 365 | ### C. Configuração do Projeto 366 | 367 | #### 1. Evite remapeamentos personalizados. 368 | 369 | [Remeapeamentos](https://book.getfoundry.sh/projects/dependencies?#remapping-dependencies) ajudam Forge a encontrar dependências com base em instruções de importação. Forge deduzirá automaticamente alguns remapeamentos, por exemplo 370 | 371 | ```rust 372 | forge-std/=lib/forge-std/src/ 373 | solmate/=lib/solmate/src/ 374 | ``` 375 | 376 | Devemos evitar adicionar a estes ou definir quaisquer remapeamentos explicitamente, pois isso torna nosso projeto mais difícil para outros usarem como uma dependência. Por exemplo, se nosso projeto depende de Solmate e o deles também, queremos evitar que nosso projeto tenha algum nome de importação irregular, resolvido com um remapeamento personalizado, que entrará em conflito com o nome de importação deles. 377 | 378 | ### D. Atualizabilidade 379 | 380 | #### 1. Prefira a convenção "Namespaced Storage Layout" do [ERC-7201](https://eips.ethereum.org/EIPS/eip-7201) para evitar colisões de armazenamento. 381 | 382 | ### E. Structs 383 | 384 | #### 1. Sempre que possível, os valores da struct devem ser compactados para minimizar SLOADs e SSTOREs. 385 | 386 | #### 2. Campos de carimbo de data/hora em uma struct devem ter pelo menos uint32 e idealmente ser uint40. 387 | 388 | `uint32` dará ao contrato cerca de 82 anos de validade `(2^32 / (60*60*24*365)) - (2024 - 1970)`. Se o espaço permitir, uint40 é o tamanho preferido. 389 | 390 | ## 3. NatSpec 391 | 392 | ### A. A menos que uma exceção ou adição seja especificamente notada, siga [Solidity NatSpec](https://docs.soliditylang.org/en/latest/natspec-format.html). 393 | 394 | ### B. Adições 395 | 396 | #### 1. Todas as funções externas, eventos e erros devem ter NatSpec completo. 397 | 398 | Minimamente incluindo um `@notice`. `@param` e `@return` devem estar presentes se houver parâmetros ou valores de retorno. 399 | 400 | #### 2. NatSpec de Struct 401 | 402 | Structs podem ser documentados com um `@notice` acima e, se desejado, `@dev` para cada campo. 403 | 404 | ```solidity 405 | /// @notice Uma struct que descreve a posição de uma conta 406 | struct Position { 407 | /// @dev O carimbo de data/hora unix (segundos) do bloco quando a posição foi criada. 408 | uint created; 409 | /// @dev A quantidade de ETH na posição 410 | uint amount; 411 | } 412 | ``` 413 | 414 | #### 3. Novas linhas entre tipos de tags. 415 | 416 | Para facilitar a leitura, adicione uma nova linha entre os tipos de tags, quando várias estão presentes e há três ou mais linhas. 417 | 418 | NÃO: 419 | 420 | ```solidity 421 | /// @notice ... 422 | /// @dev ... 423 | /// @dev ... 424 | /// @param ... 425 | /// @param ... 426 | /// @return 427 | ``` 428 | 429 | SIM: 430 | 431 | ```solidity 432 | /// @notice ... 433 | /// 434 | /// @dev ... 435 | /// @dev ... 436 | /// 437 | /// @param ... 438 | /// @param ... 439 | /// 440 | /// @return 441 | ``` 442 | 443 | #### 4. O autor deve ser a Coinbase. 444 | 445 | Se você estiver usando a tag `@author`, ela deve ser 446 | 447 | ```solidity 448 | /// @author Coinbase 449 | ``` 450 | 451 | Seguido opcionalmente por um link para o repositório público no Github. 452 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Coinbase Solidity Style Guide 2 | 3 | This is a guide for Coinbase engineers developing EVM-based smart contracts. We use Solidity when developing such contracts, so we call it a "Solidity Style Guide." This guide also covers development and testing practices. We are sharing this publicly in case it is useful to others. 4 | 5 | ## Why? 6 | 7 | We should be as specific and thorough as possible when defining our style, testing, and development practices. Any time we save not having to debate these things on pull requests is productive time that can go into other discussion and review. Following the style guide is evidence of care. 8 | 9 | ![tom-sachs-gq-style-spring-2019-05](https://github.com/coinbase/solidity-style-guide/assets/6678357/9e904107-e83f-4d89-a405-d3f1394d8de4) 10 | 11 | ## 1. Style 12 | 13 | ### A. Unless an exception or addition is specifically noted, we follow the [Solidity Style Guide](https://docs.soliditylang.org/en/latest/style-guide.html). 14 | 15 | ### B. Exceptions 16 | 17 | #### 1. Names of internal functions in a library should not have an underscore prefix. 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. 24 | 25 | > Leading underscores allow you to immediately recognize the intent of such functions... 26 | 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. 28 | 29 | ```solidity 30 | Library._function() 31 | ``` 32 | 33 | or 34 | 35 | ```solidity 36 | using Library for bytes 37 | bytes._function() 38 | ``` 39 | 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. 43 | 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 | 48 | ### C. Additions 49 | 50 | #### 1. Errors 51 | 52 | ##### A. Prefer custom errors. 53 | 54 | Custom errors are in some cases more gas efficient and allow passing useful information. 55 | 56 | ##### B. Custom error names should be CapWords style. 57 | 58 | For example, `InsufficientBalance`. 59 | 60 | #### 2. Events 61 | 62 | ##### A. Events names should be past tense. 63 | 64 | Events should track things that _happened_ and so should be past tense. Using past tense also helps avoid naming collisions with structs or functions. 65 | 66 | 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). 67 | 68 | YES: 69 | 70 | ```solidity 71 | event OwnerUpdated(address newOwner); 72 | ``` 73 | 74 | NO: 75 | 76 | ```solidity 77 | event OwnerUpdate(address newOwner); 78 | ``` 79 | 80 | ##### B. Prefer `SubjectVerb` naming format. 81 | 82 | YES: 83 | 84 | ```solidity 85 | event OwnerUpdated(address newOwner); 86 | ``` 87 | 88 | NO: 89 | 90 | ```solidity 91 | event UpdatedOwner(address newOwner); 92 | ``` 93 | 94 | #### 3. Named arguments and parameters 95 | 96 | ##### A. Avoid unnecessary named return arguments. 97 | 98 | In short functions, named return arguments are unnecessary. 99 | 100 | NO: 101 | 102 | ```solidity 103 | function add(uint a, uint b) public returns (uint result) { 104 | result = a + b; 105 | } 106 | ``` 107 | 108 | Named return arguments can be helpful in functions with multiple returned values. 109 | 110 | ```solidity 111 | function validate(UserOperation calldata userOp) external returns (bytes memory context, uint256 validationData) 112 | ``` 113 | 114 | However, it is important to be explicit when returning early. 115 | 116 | YES: 117 | 118 | ```solidity 119 | function validate(UserOperation calldata userOp) external returns (bytes memory context, uint256 validationData) { 120 | context = ""; 121 | validationData = 1; 122 | 123 | if (condition) { 124 | return (context, validationData); 125 | } 126 | } 127 | ``` 128 | 129 | NO: 130 | 131 | ```solidity 132 | function validate(UserOperation calldata userOp) external returns (bytes memory context, uint256 validationData) { 133 | context = ""; 134 | validationData = 1; 135 | if (condition) { 136 | return; 137 | } 138 | } 139 | ``` 140 | 141 | ##### B. Prefer named arguments. 142 | 143 | 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. 144 | 145 | YES: 146 | 147 | ``` 148 | pow({base: x, exponent: y, scalar: v}) 149 | ``` 150 | 151 | NO: 152 | 153 | ``` 154 | pow(x, y, v) 155 | ``` 156 | 157 | ##### C. Prefer named parameters in mapping types. 158 | 159 | Explicit naming parameters in mapping types is helpful for clarity, especially when nesting is used. 160 | 161 | YES: 162 | 163 | ``` 164 | mapping(address account => mapping(address asset => uint256 amount)) public balances; 165 | ``` 166 | 167 | NO: 168 | 169 | ``` 170 | mapping(uint256 => mapping(address => uint256)) public balances; 171 | ``` 172 | 173 | #### 4. Structure of a Contract 174 | 175 | ##### A. Prefer composition over inheritance. 176 | 177 | 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. 178 | 179 | 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). 180 | 181 | ##### B. Avoid writing interfaces. 182 | 183 | Interfaces separate NatSpec from contract logic, requiring readers to do more work to understand the code. For this reason, they should be avoided. 184 | 185 | ##### C. Avoid using assembly. 186 | 187 | Assembly code is hard to read and audit. We should avoid it unless the gas savings are very consequential, e.g. > 25%. 188 | 189 | #### 4. Versioning 190 | 191 | ##### A. Avoid unnecessary version Pragma constraints. 192 | 193 | 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 194 | 195 | ```solidity 196 | pragma solidity ^0.8.0; 197 | ``` 198 | 199 | #### 5. Struct and Error Definitions 200 | 201 | ##### A. Prefer declaring structs and errors within the interface, contract, or library where they are used. 202 | 203 | ##### 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. 204 | 205 | #### 6. Imports 206 | 207 | ##### A. Use named imports. 208 | 209 | Named imports help readers understand what exactly is being used and where it is originally declared. 210 | 211 | YES: 212 | 213 | ```solidity 214 | import {Contract} from "./contract.sol" 215 | ``` 216 | 217 | NO: 218 | 219 | ```solidity 220 | import "./contract.sol" 221 | ``` 222 | 223 | For convenience, named imports do not have to be used in test files. 224 | 225 | ##### B. Order imports alphabetically (A to Z) by file name. 226 | 227 | YES: 228 | 229 | ```solidity 230 | import {A} from './A.sol' 231 | import {B} from './B.sol' 232 | ``` 233 | 234 | NO: 235 | 236 | ```solidity 237 | import {B} from './B.sol' 238 | import {A} from './A.sol' 239 | ``` 240 | 241 | ##### C. Group imports by external and local with a new line in between. 242 | 243 | For example 244 | 245 | ```solidity 246 | import {Math} from '/solady/Math.sol' 247 | 248 | import {MyHelper} from './MyHelper.sol' 249 | ``` 250 | 251 | In test files, imports from `/test` should be their own group, as well. 252 | 253 | ```solidity 254 | import {Math} from '/solady/Math.sol' 255 | 256 | import {MyHelper} from '../src/MyHelper.sol' 257 | 258 | import {Mock} from './mocks/Mock.sol' 259 | ``` 260 | 261 | #### 7. Comments 262 | 263 | ##### A. Commenting to group sections of the code is permitted. 264 | 265 | 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. 266 | 267 | For example 268 | 269 | ```solidity 270 | /// External Functions /// 271 | ``` 272 | 273 | ```solidity 274 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ 275 | /* VALIDATION OPERATIONS */ 276 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ 277 | ``` 278 | 279 | ##### B. ASCII Art 280 | 281 | ASCII art is permitted in the space between the end of the Pragmas and the beginning of the imports. 282 | 283 | ## 2. Development 284 | 285 | ### A. Use [Forge](https://github.com/foundry-rs/foundry/tree/master/crates/forge) for testing and dependency management. 286 | 287 | ### B. Testing 288 | 289 | #### 1. Test file names should follow Solidity Style Guide conventions for files names and also have `.t` before `.sol`. 290 | 291 | For example, `ERC20.t.sol` 292 | 293 | #### 2. Test contract names should include the name of the contract or function being tested, followed by "Test". 294 | 295 | For example, 296 | 297 | - `ERC20Test` 298 | - `TransferFromTest` 299 | 300 | #### 3. Test names should follow the convention `test_functionName_outcome_optionalContext` 301 | 302 | For example 303 | 304 | - `test_transferFrom_debitsFromAccountBalance` 305 | - `test_transferFrom_debitsFromAccountBalance_whenCalledViaPermit` 306 | - `test_transferFrom_reverts_whenAmountExceedsBalance` 307 | 308 | If the contract is named after a function, then function name can be omitted. 309 | 310 | ```solidity 311 | contract TransferFromTest { 312 | function test_debitsFromAccountBalance() ... 313 | } 314 | ``` 315 | 316 | #### 4. Prefer tests that test one thing. 317 | 318 | 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. 319 | 320 | YES: 321 | 322 | ```solidity 323 | function test_transferFrom_debitsFrom() { 324 | ... 325 | } 326 | 327 | function test_transferFrom_creditsTo() { 328 | ... 329 | } 330 | 331 | function test_transferFrom_emitsCorrectly() { 332 | ... 333 | } 334 | 335 | function test_transferFrom_reverts_whenAmountExceedsBalance() { 336 | ... 337 | } 338 | ``` 339 | 340 | NO: 341 | 342 | ```solidity 343 | function test_transferFrom_works() { 344 | // debits correctly 345 | // credits correctly 346 | // emits correctly 347 | // reverts correctly 348 | } 349 | ``` 350 | 351 | 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. 352 | 353 | ```solidity 354 | function test_transferFrom_creditsTo() { 355 | assertEq(balanceOf(to), 0); 356 | ... 357 | assertEq(balanceOf(to), amount); 358 | } 359 | ``` 360 | 361 | #### 5. Use variables for important values in tests 362 | 363 | YES: 364 | 365 | ```solidity 366 | function test_transferFrom_creditsTo() { 367 | assertEq(balanceOf(to), 0); 368 | uint amount = 10; 369 | transferFrom(from, to, amount); 370 | assertEq(balanceOf(to), amount); 371 | } 372 | ``` 373 | 374 | NO: 375 | 376 | ```solidity 377 | function test_transferFrom_creditsTo() { 378 | assertEq(balanceOf(to), 0); 379 | transferFrom(from, to, 10); 380 | assertEq(balanceOf(to), 10); 381 | } 382 | ``` 383 | 384 | #### 6. Prefer fuzz tests. 385 | 386 | All else being equal, prefer fuzz tests. 387 | 388 | YES: 389 | 390 | ```solidity 391 | function test_transferFrom_creditsTo(uint amount) { 392 | assertEq(balanceOf(to), 0); 393 | transferFrom(from, to, amount); 394 | assertEq(balanceOf(to), amount); 395 | } 396 | ``` 397 | 398 | NO: 399 | 400 | ```solidity 401 | function test_transferFrom_creditsTo() { 402 | assertEq(balanceOf(to), 0); 403 | uint amount = 10; 404 | transferFrom(from, to, amount); 405 | assertEq(balanceOf(to), amount); 406 | } 407 | ``` 408 | 409 | ### C. Project Setup 410 | 411 | #### 1. Avoid custom remappings. 412 | 413 | [Remappings](https://book.getfoundry.sh/projects/dependencies?#remapping-dependencies) help Forge find dependencies based on import statements. Forge will automatically deduce some remappings, for example 414 | 415 | ```rust 416 | forge-std/=lib/forge-std/src/ 417 | solmate/=lib/solmate/src/ 418 | ``` 419 | 420 | We should avoid adding to these or defining any remappings explicitly, as it makes our project harder for others to use as a dependency. For example, if our project depends on Solmate and so does theirs, we want to avoid our project having some irregular import naming, resolved with a custom remapping, which will conflict with their import naming. 421 | 422 | ### D. Upgradability 423 | 424 | #### 1. Prefer [ERC-7201](https://eips.ethereum.org/EIPS/eip-7201) "Namespaced Storage Layout" convention to avoid storage collisions. 425 | 426 | ### E. Structs 427 | 428 | #### 1. Where possible, struct values should be packed to minimize SLOADs and SSTOREs. 429 | 430 | #### 2. Timestamp fields in a struct should be at least uint32 and ideally be uint40. 431 | 432 | `uint32` will give the contract ~82 years of validity `(2^32 / (60*60*24*365)) - (2024 - 1970)`. If space allows, uint40 is the preferred size. 433 | 434 | ## 3. NatSpec 435 | 436 | ### A. Unless an exception or addition is specifically noted, follow [Solidity NatSpec](https://docs.soliditylang.org/en/latest/natspec-format.html). 437 | 438 | ### B. Additions 439 | 440 | #### 1. All external functions, events, and errors should have complete NatSpec. 441 | 442 | Minimally including `@notice`. `@param` and `@return` should be present if there are parameters or return values. 443 | 444 | #### 2. Struct NatSpec 445 | 446 | Structs can be documented with a `@notice` above and, if desired, `@dev` for each field. 447 | 448 | ```solidity 449 | /// @notice A struct describing an accounts position 450 | struct Position { 451 | /// @dev The unix timestamp (seconds) of the block when the position was created. 452 | uint created; 453 | /// @dev The amount of ETH in the position 454 | uint amount; 455 | } 456 | ``` 457 | 458 | #### 3. Newlines between tag types. 459 | 460 | For easier reading, add a new line between tag types, when multiple are present and there are three or more lines. 461 | 462 | YES: 463 | 464 | ```solidity 465 | /// @notice ... 466 | /// 467 | /// @dev ... 468 | /// @dev ... 469 | /// 470 | /// @param ... 471 | /// @param ... 472 | /// 473 | /// @return 474 | ``` 475 | 476 | NO: 477 | 478 | ```solidity 479 | /// @notice ... 480 | /// @dev ... 481 | /// @dev ... 482 | /// @param ... 483 | /// @param ... 484 | /// @return 485 | ``` 486 | 487 | #### 4. Author should be Coinbase. 488 | 489 | If you using the `@author` tag, it should be 490 | 491 | ```solidity 492 | /// @author Coinbase 493 | ``` 494 | 495 | Optionally followed by a link to the public Github repository. 496 | --------------------------------------------------------------------------------