Guide de style

Introduction

Ce guide est destiné à fournir des conventions de codage pour l’écriture du code Solidity. Ce guide doit être considéré comme un document évolutif qui changera au fur et à mesure que des conventions utiles seront trouvées et que les anciennes conventions seront rendues obsolètes.

De nombreux projets mettront en place leurs propres guides de style. En cas de conflits, les guides de style spécifiques au projet sont prioritaires.

La structure et un grand nombre de recommandations de ce guide de style ont été tirées du guide de style de python pep8 style guide.

Le but de ce guide n’est pas d’être la bonne ou la meilleure façon d’écrire du code Solidity. Le but de ce guide est la consistance. Une citation de python pep8 résume bien ce concept.

Note

Un guide de style est une question de cohérence. La cohérence avec ce guide de style est importante. La cohérence au sein d’un module ou d’une fonction est la plus importante.

Mais le plus important : savoir quand être incohérent - parfois le guide de style ne s’applique tout simplement pas. En cas de doute, utilisez votre meilleur jugement. Regardez d’autres exemples et décidez de ce qui vous semble le mieux. Et n’hésitez pas à demander !

Présentation du code

Indentation

Utilisez 4 espaces par niveau d’indentation.

Tabs ou Espaces

Les espaces sont la méthode d’indentation préférée.

Il faut éviter de mélanger les tabulations et les espaces.

Lignes vierges

Entourer les déclarations de haut niveau dans le code source de solidity de deux lignes vides.

Oui :

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;

contract A {
    // ...
}


contract B {
    // ...
}


contract C {
    // ...
}

Non :

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;

contract A {
    // ...
}
contract B {
    // ...
}

contract C {
    // ...
}

Dans un contrat, les déclarations de fonctions sont entourées d’une seule ligne vierge.

Les lignes vides peuvent être omises entre des groupes de déclarations d’une seule ligne (comme les fonctions de base d’un contrat abstrait).

Oui :

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;

abstract contract A {
    function spam() public virtual pure;
    function ham() public virtual pure;
}


contract B is A {
    function spam() public pure override {
        // ...
    }

    function ham() public pure override {
        // ...
    }
}

Non :

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;

abstract contract A {
    function spam() virtual pure public;
    function ham() public virtual pure;
}


contract B is A {
    function spam() public pure override {
        // ...
    }
    function ham() public pure override {
        // ...
    }
}

Longueur maximale de la ligne

Garder les lignes sous la recommandation PEP 8 à un maximum de 79 (ou 99) caractères aide les lecteurs à analyser facilement le code.

Les lignes enveloppées doivent se conformer aux directives suivantes.

  1. Le premier argument ne doit pas être attaché à la parenthèse ouvrante.

  2. Une, et une seule, indentation doit être utilisée.

  3. Chaque argument doit être placé sur sa propre ligne.

  4. L’élément de terminaison, );, doit être placé seul sur la dernière ligne.

Appels de fonction

Oui :

thisFunctionCallIsReallyLong(
    longArgument1,
    longArgument2,
    longArgument3
);

Non :

thisFunctionCallIsReallyLong(longArgument1,
                              longArgument2,
                              longArgument3
);

thisFunctionCallIsReallyLong(longArgument1,
    longArgument2,
    longArgument3
);

thisFunctionCallIsReallyLong(
    longArgument1, longArgument2,
    longArgument3
);

thisFunctionCallIsReallyLong(
longArgument1,
longArgument2,
longArgument3
);

thisFunctionCallIsReallyLong(
    longArgument1,
    longArgument2,
    longArgument3);

Déclarations d’affectation

Oui :

thisIsALongNestedMapping[being][set][to_some_value] = someFunction(
    argument1,
    argument2,
    argument3,
    argument4
);

Non :

thisIsALongNestedMapping[being][set][to_some_value] = someFunction(argument1,
                                                                   argument2,
                                                                   argument3,
                                                                   argument4);

Définitions d’événements et émetteurs d’événements

Oui :

event LongAndLotsOfArgs(
    address sender,
    address recipient,
    uint256 publicKey,
    uint256 amount,
    bytes32[] options
);

LongAndLotsOfArgs(
    sender,
    recipient,
    publicKey,
    amount,
    options
);

Non « 

event LongAndLotsOfArgs(address sender,
                        address recipient,
                        uint256 publicKey,
                        uint256 amount,
                        bytes32[] options);

LongAndLotsOfArgs(sender,
                  recipient,
                  publicKey,
                  amount,
                  options);

Codage du fichier source

L’encodage UTF-8 ou ASCII est préféré.

Importations

Les déclarations d’importation doivent toujours être placées en haut du fichier.

Oui :

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;

import "./Owned.sol";

contract A {
    // ...
}

contract B is Owned {
    // ...
}

Non :

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;

contract A {
    // ...
}


import "./Owned.sol";


contract B is Owned {
    // ...
}

Ordre des fonctions

L’ordre aide les lecteurs à identifier les fonctions qu’ils peuvent appeler et à trouver plus facilement les définitions des constructeurs et des fonctions de repli.

Les fonctions doivent être regroupées en fonction de leur visibilité et ordonnées :

  • constructor

  • receive function (si elle existe)

  • fallback function (si elle existe)

  • external

  • public

  • internal

  • private

Dans un regroupement, placez les fonctions view et pure en dernier.

Oui :

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract A {
    constructor() {
        // ...
    }

    receive() external payable {
        // ...
    }

    fallback() external {
        // ...
    }

    // Fonctions externes
    // ...

    // Fonctions externes qui sont view
    // ...

    // Fonctions externes qui sont pure
    // ...

    // Fonctions publiques
    // ...

    // Fonctions internes
    // ...

    // Fonctions privées
    // ...
}

Non :

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract A {

    // External functions
    // ...

    fallback() external {
        // ...
    }
    receive() external payable {
        // ...
    }

    // Fonctions privées
    // ...

    // Fonctions publiques
    // ...

    constructor() {
        // ...
    }

    // Fonctions internes
    // ...
}

Espaces blancs dans les expressions

Évitez les espaces blancs superflus dans les situations suivantes :

Immédiatement à l’intérieur des parenthèses, des crochets ou des accolades, à l’exception des déclarations de fonctions sur une seule ligne.

Oui :

spam(ham[1], Coin({name: "ham"}));

Non :

spam( ham[ 1 ], Coin( { name: "ham" } ) );

Exception :

function singleLine() public { spam(); }

Immédiatement avant une virgule, un point-virgule :

Oui :

function spam(uint i, Coin coin) public;

Non;

function spam(uint i , Coin coin) public ;

More than one space around an assignment or other operator to align with another:

Yes:

x = 1;
y = 2;
long_variable = 3;

Non :

x             = 1;
y             = 2;
long_variable = 3;

Ne pas inclure d’espace dans les fonctions de réception et de repli :

Oui :

receive() external payable {
    ...
}

fallback() external {
    ...
}

Non :

receive () external payable {
    ...
}

fallback () external {
    ...
}

Structures de contrôle

Les accolades désignant le corps d’un contrat, d’une bibliothèque, de fonctions et de structs doivent :

  • s’ouvrir sur la même ligne que la déclaration

  • se fermer sur leur propre ligne au même niveau d’indentation que le début de la déclaration.

  • L’accolade d’ouverture doit être précédée d’un espace.

Oui :

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;

contract Coin {
    struct Bank {
        address owner;
        uint balance;
    }
}

Non :

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;

contract Coin
{
    struct Bank {
        address owner;
        uint balance;
    }
}

Les mêmes recommandations s’appliquent aux structures de contrôle if, else, while, et for.

En outre, les structures de contrôle suivantes doivent être séparées par un espace unique if, while et for et le bloc entre parenthèses représentant le conditionnel, ainsi qu’un espace entre le bloc parenthétique conditionnel et l’accolade ouvrante.

Oui :

if (...) {
    ...
}

for (...) {
    ...
}

Non :

if (...)
{
    ...
}

while(...){
}

for (...) {
    ...;}

Pour les structures de contrôle dont le corps contient une seule déclaration, l’omission des accolades est acceptable si la déclaration est contenue sur une seule ligne.

Oui :

if (x < 10)
    x += 1;

Non :

if (x < 10)
    someArray.push(Coin({
        name: 'spam',
        value: 42
    }));

Pour les blocs if qui ont une clause else ou else if, la clause else doit être placée sur la même ligne que l’accolade fermant le bloc if. Il s’agit d’une exception par rapport aux règles des autres structures de type bloc.

Oui :

if (x < 3) {
    x += 1;
} else if (x > 7) {
    x -= 1;
} else {
    x = 5;
}


if (x < 3)
    x += 1;
else
    x -= 1;

Non :

if (x < 3) {
    x += 1;
}
else {
    x -= 1;
}

Déclaration de fonction

Pour les déclarations de fonction courtes, il est recommandé de garder l’accolade d’ouverture du corps de la fonction sur la même ligne que la déclaration de la fonction.

L’accolade fermante doit être au même niveau d’indentation que la déclaration de fonction. de la fonction.

L’accolade ouvrante doit être précédée d’un seul espace.

Oui :

function increment(uint x) public pure returns (uint) {
    return x + 1;
}

function increment(uint x) public pure onlyOwner returns (uint) {
    return x + 1;
}

Non :

function increment(uint x) public pure returns (uint)
{
    return x + 1;
}

function increment(uint x) public pure returns (uint){
    return x + 1;
}

function increment(uint x) public pure returns (uint) {
    return x + 1;
    }

function increment(uint x) public pure returns (uint) {
    return x + 1;}

L’ordre des modificateurs pour une fonction doit être :

  1. Visibilité

  2. Mutabilité

  3. Virtuel

  4. Remplacer

  5. Modificateurs personnalisés

Oui :

function balance(uint from) public view override returns (uint)  {
    return balanceOf[from];
}

function shutdown() public onlyOwner {
    selfdestruct(owner);
}

Non :

function balance(uint from) public override view returns (uint)  {
    return balanceOf[from];
}

function shutdown() onlyOwner public {
    selfdestruct(owner);
}

Pour les longues déclarations de fonctions, il est recommandé de déposer chaque argument sur sa propre ligne au même niveau d’indentation que le corps de la fonction. La parenthèse fermante et la parenthèse ouvrante doivent être placées sur leur propre ligne au même niveau d’indentation que la déclaration de fonction.

Oui :

function thisFunctionHasLotsOfArguments(
    address a,
    address b,
    address c,
    address d,
    address e,
    address f
)
    public
{
    doSomething();
}

Non :

function thisFunctionHasLotsOfArguments(address a, address b, address c,
    address d, address e, address f) public {
    doSomething();
}

function thisFunctionHasLotsOfArguments(address a,
                                        address b,
                                        address c,
                                        address d,
                                        address e,
                                        address f) public {
    doSomething();
}

function thisFunctionHasLotsOfArguments(
    address a,
    address b,
    address c,
    address d,
    address e,
    address f) public {
    doSomething();
}

Si une longue déclaration de fonction comporte des modificateurs, chaque modificateur doit être déposé sur sa propre ligne.

Oui :

function thisFunctionNameIsReallyLong(address x, address y, address z)
    public
    onlyOwner
    priced
    returns (address)
{
    doSomething();
}

function thisFunctionNameIsReallyLong(
    address x,
    address y,
    address z
)
    public
    onlyOwner
    priced
    returns (address)
{
    doSomething();
}

Non :

function thisFunctionNameIsReallyLong(address x, address y, address z)
                                      public
                                      onlyOwner
                                      priced
                                      returns (address) {
    doSomething();
}

function thisFunctionNameIsReallyLong(address x, address y, address z)
    public onlyOwner priced returns (address)
{
    doSomething();
}

function thisFunctionNameIsReallyLong(address x, address y, address z)
    public
    onlyOwner
    priced
    returns (address) {
    doSomething();
}

Les paramètres de sortie et les instructions de retour multilignes doivent suivre le même style que celui recommandé pour l’habillage des longues lignes dans la section Longueur de ligne maximale.

Oui :

function thisFunctionNameIsReallyLong(
    address a,
    address b,
    address c
)
    public
    returns (
        address someAddressName,
        uint256 LongArgument,
        uint256 Argument
    )
{
    doSomething()

    return (
        veryLongReturnArg1,
        veryLongReturnArg2,
        veryLongReturnArg3
    );
}

Non :

function thisFunctionNameIsReallyLong(
    address a,
    address b,
    address c
)
    public
    returns (address someAddressName,
             uint256 LongArgument,
             uint256 Argument)
{
    doSomething()

    return (veryLongReturnArg1,
            veryLongReturnArg1,
            veryLongReturnArg1);
}

Pour les fonctions constructrices sur les contrats hérités dont les bases nécessitent des arguments, il est recommandé de déposer les constructeurs de base sur de nouvelles lignes de la même manière que les modificateurs si la déclaration de la fonction est longue ou difficile à lire.

Oui :

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
// Contrats de base juste pour que cela compile
contract B {
    constructor(uint) {
    }
}
contract C {
    constructor(uint, uint) {
    }
}
contract D {
    constructor(uint) {
    }
}

contract A is B, C, D {
    uint x;

    constructor(uint param1, uint param2, uint param3, uint param4, uint param5)
        B(param1)
        C(param2, param3)
        D(param4)
    {
        // do something with param5
        x = param5;
    }
}

Non :

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

// Contrats de base juste pour que cela compile
contract B {
    constructor(uint) {
    }
}


contract C {
    constructor(uint, uint) {
    }
}


contract D {
    constructor(uint) {
    }
}


contract A is B, C, D {
    uint x;

    constructor(uint param1, uint param2, uint param3, uint param4, uint param5)
    B(param1)
    C(param2, param3)
    D(param4) {
        x = param5;
    }
}


contract X is B, C, D {
    uint x;

    constructor(uint param1, uint param2, uint param3, uint param4, uint param5)
        B(param1)
        C(param2, param3)
        D(param4) {
            x = param5;
        }
}

Lorsque vous déclarez des fonctions courtes avec une seule déclaration, il est permis de le faire sur une seule ligne.

C’est autorisé :

function shortFunction() public { doSomething(); }

Ces directives pour les déclarations de fonctions sont destinées à améliorer la lisibilité. Les auteurs doivent faire preuve de discernement car ce guide ne prétend pas couvrir toutes les permutations possibles pour les déclarations de fonctions.

Mappages

Dans les déclarations de variables, ne séparez pas le mot-clé mapping de son type par un espace. Ne séparez pas un mot-clé mapping imbriqué de son type par un espace.

Oui :

mapping(uint => uint) map;
mapping(address => bool) registeredAddresses;
mapping(uint => mapping(bool => Data[])) public data;
mapping(uint => mapping(uint => s)) data;

Non :

mapping (uint => uint) map;
mapping( address => bool ) registeredAddresses;
mapping (uint => mapping (bool => Data[])) public data;
mapping(uint => mapping (uint => s)) data;

Déclarations de variables

Les déclarations de variables de tableau ne doivent pas comporter d’espace entre le type et les parenthèses.

Oui :

uint[] x;

Non :

uint [] x;

Autres recommandations

  • Les chaînes de caractères devraient être citées avec des guillemets doubles au lieu de guillemets simples.

Oui :

str = "foo";
str = "Hamlet dit : 'Être ou ne pas être...'";

Non :

str = 'bar';
str = '"Soyez vous-même ; tous les autres sont déjà pris." -Oscar Wilde';
  • Entourer les opérateurs d’un espace unique de chaque côté.

Oui :

x = 3;
x = 100 / 10;
x += 3 + 4;
x |= y && z;

Non :

x=3;
x = 100/10;
x += 3+4;
x |= y&&z;
  • Les opérateurs ayant une priorité plus élevée que les autres peuvent exclure les espaces afin d’indiquer la préséance. Ceci a pour but de permettre d’améliorer la lisibilité d’une déclaration complexe. Vous devez toujours utiliser la même quantité d’espaces blancs de part et d’autre d’un opérateur :

Oui :

x = 2**3 + 5;
x = 2*y + 3*z;
x = (a+b) * (a-b);

Non :

x = 2** 3 + 5;
x = y+z;
x +=1;

Ordre de mise en page

Disposez les éléments du contrat dans l’ordre suivant :

  1. Déclarations de pragmatisme

  2. Instructions d’importation

  3. Interfaces

  4. Bibliothèques

  5. Contrats

À l’intérieur de chaque contrat, bibliothèque ou interface, utilisez l’ordre suivant :

  1. Les déclarations de type

  2. Variables d’état

  3. Événements

  4. Fonctions

Note

Il peut être plus clair de déclarer les types à proximité de leur utilisation dans les événements ou les variables d’état.

Conventions d’appellation

Les conventions de dénomination sont puissantes lorsqu’elles sont adoptées et utilisées à grande échelle. L’utilisation de différentes conventions peut véhiculer des informations méta significatives qui, autrement, ne seraient pas immédiatement disponibles.

Les recommandations de nommage données ici sont destinées à améliorer la lisibilité, et ne sont donc pas des règles, mais plutôt des lignes directrices pour essayer d’aider à transmettre le plus d’informations à travers les noms des choses.

Enfin, la cohérence au sein d’une base de code devrait toujours prévaloir sur les conventions décrites dans ce document.

Styles de dénomination

Pour éviter toute confusion, les noms suivants seront utilisés pour faire référence à différents styles d’appellation.

  • b (lettre minuscule simple)

  • B (lettre majuscule simple)

  • lettresminuscules

  • minuscule_avec_underscores

  • MAJUSCULE

  • MAJUSCULE_AVEC_UNDERSCORES

  • MotsEnMajuscule (ou MotsEnMaj)

  • casMixe (diffère des CapitalizedWords par le caractère minuscule initial !)

  • Mots_Capitalisés_Avec_Underscores

Note

Lorsque vous utilisez des sigles dans CapWords, mettez toutes les lettres des sigles en majuscules. Ainsi, HTTPServerError est préférable à HttpServerError. Lors de l’utilisation d’initiales en mixedCase, mettez toutes les lettres des initiales en majuscules, mais gardez la première en minuscule si elle est le début du nom. Ainsi, xmlHTTPRequest est préférable à XMLHTTPRequest.

Noms à éviter

  • l - Lettre minuscule el

  • O - Lettre majuscule oh

  • I - Lettre majuscule eye

N’utilisez jamais l’un de ces noms pour des noms de variables à une seule lettre. Elles sont souvent impossibles à distinguer des chiffres un et zéro.

Noms de contrats et de bibliothèques

  • Les contrats et les bibliothèques doivent être nommés en utilisant le style CapWords. Exemples : SimpleToken, SmartBank, CertificateHashRepository, Player, Congress, Owned.

  • Les noms des contrats et des bibliothèques doivent également correspondre à leurs noms de fichiers.

  • Si un fichier de contrat comprend plusieurs contrats et/ou bibliothèques, alors le nom du fichier doit correspondre au contrat principal. Cela n’est cependant pas recommandé si cela peut être évité.

Comme le montre l’exemple ci-dessous, si le nom du contrat est Congress et celui de la bibliothèque Owned, les noms de fichiers associés doivent être Congress.sol et Owned.sol.

Oui :

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

// Owned.sol
contract Owned {
    address public owner;

    constructor() {
        owner = msg.sender;
    }

    modifier onlyOwner {
        require(msg.sender == owner);
        _;
    }

    function transferOwnership(address newOwner) public onlyOwner {
        owner = newOwner;
    }
}

et dans Congress.sol :

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;

import "./Owned.sol";


contract Congress is Owned, TokenRecipient {
    //...
}

Non :

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

// owned.sol
contract owned {
    address public owner;

    constructor() {
        owner = msg.sender;
    }

    modifier onlyOwner {
        require(msg.sender == owner);
        _;
    }

    function transferOwnership(address newOwner) public onlyOwner {
        owner = newOwner;
    }
}

et dans Congress.sol:

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.7.0;


import "./owned.sol";


contract Congress is owned, tokenRecipient {
    //...
}

Noms de structures

Les structures doivent être nommées en utilisant le style CapWords. Exemples :MonCoin, Position, PositionXY.

Noms d’événements

Les événements doivent être nommés en utilisant le style CapWords. Exemples : Dépôt, Transfert, Approbation, AvantTransfert, AprèsTransfert.

Noms des fonctions

Les fonctions doivent utiliser la casse mixte. Exemples : getBalance, transfer, verifyOwner, addMember, changeOwner.

Noms des arguments de la fonction

Les arguments des fonctions doivent utiliser des majuscules et des minuscules. Exemples : initialSupply, account, recipientAddress, senderAddress, newOwner.

Lorsque vous écrivez des fonctions de bibliothèque qui opèrent sur un struct personnalisé, le struct doit être le premier argument et doit toujours être nommée self.

Noms des variables locales et des variables d’état

Utilisez la casse mixte. Exemples : totalSupply, remainingSupply, balancesOf, creatorAddress, isPreSale, tokenExchangeRate.

Constantes

Les constantes doivent être nommées avec des lettres majuscules et des caractères de soulignement pour séparer les mots. Exemples : MAX_BLOCKS, TOKEN_NAME, TOKEN_TICKER, CONTRACT_VERSION.

Noms des modificateurs

Utilisez la casse mixte. Exemples : onlyBy, onlyAfter, onlyDuringThePreSale.

Enums

Les Enums, dans le style des déclarations de type simples, doivent être nommés en utilisant le style CapWords. Exemples : TokenGroup, Frame, HashStyle, CharacterLocation.

Éviter les collisions de noms

  • single_trailing_underscore_

Cette convention est suggérée lorsque le nom souhaité entre en collision avec celui d’un nom intégré ou autrement réservé.

NatSpec

Les contrats Solidity peuvent également contenir des commentaires NatSpec. Ils sont écrits avec une triple barre oblique (///) ou un double astérisque (/** ... */). Ils doivent être utilisés directement au-dessus des déclarations de fonctions ou des instructions.

Par exemple, le contrat de un smart contract simple avec les commentaires ajoutés, ressemble à celui ci-dessous :

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

/// @author L'équipe Solidity
/// @title Un exemple simple de stockage
contract SimpleStorage {
    uint storedData;

    /// Stocke `x`.
    /// @param x la nouvelle valeur à stocker
    /// @dev stocke le nombre dans la variable d'état `storedData`.
    function set(uint x) public {
        storedData = x;
    }

    /// Retourner la valeur stockée.
    /// @dev récupère la valeur de la variable d'état `storedData`.
    /// @retourne la valeur stockée
    function get() public view returns (uint) {
        return storedData;
    }
}

Il est recommandé que les contrats Solidity soient entièrement annotés en utilisant NatSpec pour toutes les interfaces publiques (tout ce qui se trouve dans l’ABI).

Veuillez consulter la section sur NatSpec pour une explication détaillée.