スタイルガイド

イントロダクション

このガイドは、Solidityのコーディング規約を提供することを目的としています。 このガイドは、有用な規約が発見されたり、古い規約が廃止されたりして、時間とともに変化していく文書として考えるべきです。

多くのプロジェクトでは、独自のスタイルガイドを導入しています。 コンフリクトが生じた場合は、プロジェクト独自のスタイルガイドが優先されます。

このスタイルガイドの構造や推奨事項の多くは、Pythonの pep8スタイルガイド から引用されています。

このガイドの目的は、Solidityのコードを書くための正しい方法や最良の方法であることではありません。 このガイドの目的は、 一貫性 です。 Pythonの pep8 からの引用はこのコンセプトをよく表しています。

注釈

スタイルガイドとは一貫性を保つためのものです。 このスタイルガイドとの一貫性は重要です。 プロジェクト内での一貫性はより重要です。 そして、最も重要な一貫性は、一つのモジュールや関数の中での一貫性です。

しかし、一番重要なことは、 一貫性がないことを自覚すること です。 時には、スタイルガイドをそのまま適用できないこともあります。 迷ったときは、自分のベストな判断に従ってください。 また、他の例を見て何がベストなのかを判断してください。 そして、迷わず質問してください!

コードレイアウト

インデント

インデントレベルごとに4つのスペースを使用してください。

タブかスペースか

スペースのほうが好ましいインデント方法です。

タブとスペースの混在は避けてください。

空行

Solidityのソースコードでは、トップレベルの宣言を2つの空行で囲んでください。

OK:

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

contract A {
    // ...
}

contract B {
    // ...
}

contract C {
    // ...
}

NG:

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

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

contract C {
    // ...
}

コントラクト内では、関数の宣言を1つの空行で囲みます。

関連する一行のグループにおいては、空行を省略しても良いです(抽象コントラクトの未実装の関数など)。

OK:

// 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 {
        // ...
    }
}

NG:

// 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 {
        // ...
    }
}

最大の行の長さ

行の長さは最大120文字であることを推奨しています。

ラップされる行は以下のガイドラインに沿ってください。

  1. 第1引数は、開始括弧に付けてはいけません。

  2. インデントは1つだけにしてください。

  3. それぞれの引数は、それぞれの行にあるべきものです。

  4. 終端要素である ); は、それだけを最後の行に配置する必要があります。

関数呼び出し

OK:

thisFunctionCallIsReallyLong(
    longArgument1,
    longArgument2,
    longArgument3
);

NG:

thisFunctionCallIsReallyLong(longArgument1,
                              longArgument2,
                              longArgument3
);

thisFunctionCallIsReallyLong(longArgument1,
    longArgument2,
    longArgument3
);

thisFunctionCallIsReallyLong(
    longArgument1, longArgument2,
    longArgument3
);

thisFunctionCallIsReallyLong(
longArgument1,
longArgument2,
longArgument3
);

thisFunctionCallIsReallyLong(
    longArgument1,
    longArgument2,
    longArgument3);

代入文

OK:

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

NG:

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

イベント定義とイベントエミッタ

OK:

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

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

NG:

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

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

ソースファイルのエンコーディング

UTF-8あるいはASCIIのエンコーディングが望ましいです。

インポート

インポート文は、常にファイルの先頭に配置する必要があります。

OK:

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

import "./Owned.sol";

contract A {
    // ...
}


contract B is Owned {
    // ...
}

NG:

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

contract A {
    // ...
}

import "./Owned.sol";

contract B is Owned {
    // ...
}

関数の順番

順番を決めることで、読者はどの関数を呼び出すことができるかを識別し、コンストラクタやフォールバックの定義を見つけやすくなります。

関数はビジビリティに応じてグループ化し、順序立てて配置します。

  • constructor

  • receive 関数(ある場合)

  • fallback 関数(ある場合)

  • external 関数

  • public 関数

  • internal 関数

  • private 関数

グループ内では、 view 関数と pure 関数を最後に配置します。

OK:

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

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

    fallback() external {
        // ...
    }

    // External functions
    // ...

    // External functions that are view
    // ...

    // External functions that are pure
    // ...

    // Public functions
    // ...

    // Internal functions
    // ...

    // Private functions
    // ...
}

NG:

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

    // External functions
    // ...

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

    // Private functions
    // ...

    // Public functions
    // ...

    constructor() {
        // ...
    }

    // Internal functions
    // ...
}

式中の空白文字

次の場合は、余計な空白を入れないようにしましょう。

括弧、大括弧、中括弧のすぐ内側(ただし、1行の関数の宣言は例外):

OK:

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

NG:

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

例外:

function singleLine() public { spam(); }

コンマとセミコロンの直前:

OK:

function spam(uint i, Coin coin) public;

NG:

function spam(uint i , Coin coin) public ;

代入や他の演算子の周りに1つ以上の空白を入れての整列:

OK:

x = 1;
y = 2;
longVariable = 3;

NG:

x            = 1;
y            = 2;
longVariable = 3;

レシーブ関数とフォールバック関数に空白を入れてはいけません:

OK:

receive() external payable {
    ...
}

fallback() external {
    ...
}

NG:

receive () external payable {
    ...
}

fallback () external {
    ...
}

制御構造

コントラクト、ライブラリ、関数、構造体の本体を示す中括弧は、次のようにします。

  • 宣言と同じ行にオープンする

  • 宣言の先頭と同じインデントレベルで独立した行でクローズする

  • 冒頭のブレースの前に半角スペースを入れる

OK:

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

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

NG:

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

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

制御構造 ifelsewhilefor にも同じ推奨事項が適用されます。

また、制御構造 ifwhilefor と条件を表す親ブロックの間には半角スペースを入れ、条件を表す親ブロックと開始ブレースの間にも半角スペースを入れる必要があります。

OK:

if (...) {
    ...
}

for (...) {
    ...
}

NG:

if (...)
{
    ...
}

while(...){
}

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

本体が1つの文を含む制御構造の場合、文が1行に収まっていれば、中括弧を省略しても問題ありません。

OK:

if (x < 10)
    x += 1;

NG:

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

else または else if 句を持つ if ブロックでは、 elseif の閉じ括弧と同じ行に配置します。 これは、他のブロックのような構造のルールに比べて例外的なものです。

OK:

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

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

NG:

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

関数宣言

短い関数宣言の場合は、関数本体の開始波括弧を関数宣言と同じ行に置くことをお勧めします。

閉じ括弧は、関数宣言と同じインデントレベルでなければなりません。

冒頭のブレースの前には半角スペースを入れてください。

OK:

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

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

NG:

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;}

関数の修飾順序は次のようになります。

  1. ビジビリティ

  2. ミュータビリティ

  3. バーチャル

  4. オーバーライド

  5. カスタムモディファイア

OK:

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

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

NG:

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

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

長い関数宣言の場合は、各引数を関数本体と同じインデントレベルで一行にまとめることをお勧めします。 閉じ括弧と開き括弧も同様に、関数宣言と同じインデントレベルで一行に置く必要があります。

OK:

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

NG:

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();
}

長い関数宣言にモディファイアがある場合は、各モディファイアをそれぞれの行に落とす必要があります。

OK:

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();
}

NG:

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();
}

複数行の出力パラメータやreturn文は、 最大の行の長さ セクションで推奨されている長い行の折り返しと同じスタイルにしてください。

OK:

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

    return (
        veryLongReturnArg1,
        veryLongReturnArg2,
        veryLongReturnArg3
    );
}

NG:

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

    return (veryLongReturnArg1,
            veryLongReturnArg1,
            veryLongReturnArg1);
}

ベースが引数を必要とする継承されたコントラクトのコンストラクタ関数については、関数宣言が長い場合や読みにくい場合には、モディファイアと同じ方法でベースのコンストラクタを新しい行に落とすことをお勧めします。

OK:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
// Base contracts just to make this 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;
    }
}

NG:

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

// Base contracts just to make this 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;
        }
}

短い関数を1つの文で宣言する場合、1行で宣言しても構いません。

許可されています。

function shortFunction() public { doSomething(); }

この関数宣言のガイドラインは、読みやすさを向上させることを目的としています。 このガイドラインは、関数宣言のすべての可能性を網羅するものではありませんので、執筆者は最善の判断を下す必要があります。

マッピング

変数宣言では、キーワード mapping とその型は空白で区切りません。 また、ネストした mapping キーワードとその型は空白で区切りません。

OK:

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

NG:

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

変数宣言

配列変数の宣言では、型と括弧の間にスペースを入れてはいけません。

OK:

uint[] x;

NG:

uint [] x;

その他の推奨事項

  • 文字列は、シングルクォートではなくダブルクォートで引用してください。

OK:

str = "foo";
str = "Hamlet says, 'To be or not to be...'";

NG:

str = 'bar';
str = '"Be yourself; everyone else is already taken." -Oscar Wilde';
  • 演算子を左右の半角スペースで囲みます。

OK:

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

NG:

x=3;
x = 100/10;
x += 3+4;
x |= y&&z;
  • 優先順位の高い演算子は、優先順位を示すために周囲の空白を除外できます。 これは、複雑な文の可読性を高めるためのものです。 演算子の両側には、常に同じ量の空白を使用する必要があります。

OK:

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

NG:

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

レイアウトの順序

コントラクトの要素を以下の順序でレイアウトします。

  1. プラグマ文

  2. インポート文

  3. インターフェース

  4. ライブラリ

  5. コントラクト

各コントラクト、ライブラリ、インターフェースの内部では、以下の順序を使用します。

  1. 型の宣言

  2. 状態変数

  3. イベント

  4. エラー

  5. モディファイア

  6. 関数

注釈

イベントや状態変数での使用に近い形で型を宣言した方がわかりやすいかもしれません。

OK:

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

abstract contract Math {
    error DivideByZero();
    function divide(int256 numerator, int256 denominator) public virtual returns (uint256);
}

NG:

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

abstract contract Math {
    function divide(int256 numerator, int256 denominator) public virtual returns (uint256);
    error DivideByZero();
}

命名規則

命名規則は、広く採用され使用されることで力を発揮します。 異なる規約を使用することで、他の方法ではすぐには得られない重要なメタ情報を伝えることができます。

ここで述べられているネーミングの推奨事項は、読みやすさを向上させることを目的としているため、ルールではなく、物事の名前を通して最も多くの情報を伝えるためのガイドラインとなっています。

最後に、コードベース内の一貫性は、常にこのドキュメントで説明されている規約よりも優先されるべきです。

命名スタイル

混乱を避けるために、以下の名称は異なるネーミングスタイルを参照するために使用されます。

  • b (半角英小文字)

  • B (半角英大文字)

  • lowercase

  • UPPERCASE

  • UPPER_CASE_WITH_UNDERSCORES

  • CapitalizedWords (またはCapWords)

  • mixedCase (CapitalizedWordsとの違いは、頭文字が小文字であること!)

注釈

CapWordsで頭文字を使用する場合は、頭文字のすべての文字を大文字にします。 したがって、HttpServerErrorよりもHTTPServerErrorの方がよいです。 頭文字をmixedCaseで使用する場合は、頭文字の文字をすべて大文字にします。 ただし、名前の先頭の文字は小文字にします。 したがって、xmlHTTPRequestの方がXMLHTTPRequestよりも優れています。

避けるべき名前

  • l - 小文字のエル

  • O - 大文字のオー

  • I - 大文字のアイ

これらは一文字の変数名には絶対に使用しないでください。 これらは、数字のoneやzeroと区別がつかないことがあります。

コントラクトとライブラリの名前

  • コントラクトやライブラリの名前は、CapWordsスタイルを使用してください。 例: SimpleToken, SmartBank, CertificateHashRepository, Player, Congress, Owned

  • コントラクト名とライブラリ名は、ファイル名と一致している必要があります。

  • コントラクトファイルに複数のコントラクトやライブラリが含まれている場合、ファイル名は コアコントラクト と一致させる必要があります。しかし、これは避けることができるならば、推奨されません。

以下の例のように、コントラクト名が Congress 、ライブラリ名が Owned の場合、関連するファイル名は Congress.solOwned.sol になります。

OK:

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

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

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

    constructor() {
        owner = msg.sender;
    }

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

そして、 Congress.sol で次のコードになっています。

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

import "./Owned.sol";

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

NG:

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

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

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

    constructor() {
        owner = msg.sender;
    }

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

そして、 Congress.sol で次のコードになっています。

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

import "./owned.sol";

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

構造体名

構造体の名前は、CapWordsスタイルを使用してください。 例: MyCoinPositionPositionXY

イベント名

イベント名は、CapWordsスタイルを使用してください。 例: Deposit, Transfer, Approval, BeforeTransfer, AfterTransfer

関数名

関数はmixedCaseを使用してください。 例: getBalance, transfer, verifyOwner, addMember, changeOwner

関数の引数名

関数の引数には、mixedCaseを使用してください。 例: initialSupply, account, recipientAddress, senderAddress, newOwner

カスタム構造体を操作するライブラリ関数を書くときは、構造体を第1引数にして、常に self という名前にしてください。

ローカル変数名と状態変数名

mixedCaseを使用してください。 例: totalSupply, remainingSupply, balancesOf, creatorAddress, isPreSale, tokenExchangeRate

定数

定数の名前は、すべて大文字で、アンダースコアで単語を区切ってください。 例: MAX_BLOCKS, TOKEN_NAME, TOKEN_TICKER, CONTRACT_VERSION

モディファイア名

mixedCaseを使用してください。 例: onlyByonlyAfteronlyDuringThePreSale

列挙

列挙(enum)は、単純な型宣言のスタイルで、CapWordsスタイルを使用してください。 例: TokenGroup, Frame, HashStyle, CharacterLocation

名前の衝突の回避

  • singleTrailingUnderscore_

この規約は、希望する名前が、既存の状態変数、関数、組み込み、またはその他の予約名と衝突する場合に提案されます。

非外部関数と非外部変数のためのアンダースコア接頭辞

  • _singleLeadingUnderscore

この規約は、非外部関数と非外部状態変数(つまり private または internal )で推奨されています。 ビジビリティの指定がない状態変数は、デフォルトで internal となります。

スマートコントラクトを設計する際、public-facing API(どのアカウントからも呼び出せる関数)は重要な検討事項です。 アンダースコアを付けると、そのような関数の意図をすぐに認識できますが、より重要なのは、関数を非外部から外部( public を含む)に変更し、それに応じて名前を変更すると、名前を変更する際にすべての呼び出しサイトを確認しなければならない点です。 これは、意図しない外部関数に対する重要な手動チェックであり、セキュリティ脆弱性の一般的な原因でもあります(この変更のためのfind-replace-allツールは避けてください)。

NatSpec

Solidityのコントラクトには、NatSpecコメントを含めることができます。 コメントはトリプルスラッシュ( /// )またはダブルアスタリスクブロック( /** ... */ )で記述し、関数宣言や文の直上で使用する必要があります。

例えば、 シンプルなスマートコントラクト のコントラクトにコメントを加えたものは、次のようになります。

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

/// @author The Solidity Team
/// @title A simple storage example
contract SimpleStorage {
    uint storedData;

    /// Store `x`.
    /// @param x the new value to store
    /// @dev stores the number in the state variable `storedData`
    function set(uint x) public {
        storedData = x;
    }

    /// Return the stored value.
    /// @dev retrieves the value of the state variable `storedData`
    /// @return the stored value
    function get() public view returns (uint) {
        return storedData;
    }
}

Solidityのコントラクトは、すべてのパブリックインターフェース(ABIのすべて)に対して NatSpec を使って完全にアノテーションすることを推奨します。

詳しい説明は NatSpec の項を参照してください。