コンテンツへスキップ

Visual Studio Code (Brownie)

環境準備

  1. Visual Studio CodeMicrosoft C++ Build ToolsPythonGanacheNodeJSがインストールされていなければダウンロードしてインストールする。Microsoft C++ Build Toolsをインストールする際は「C++によるデスクトップ開発」を含めるのを忘れない。
  2. Visual Studio Codeを開き、Solidityの拡張機能をインストールする。
  3. Visual Studio CodeのTerminalを開き、必要なパッケージをインストールする。
pip install black
pip install py-solc-x
pip install web3
  1. Visual Studio Codeの設定で以下の設定をする。
    ・「Format On Save」をオン
    ・「JSON > Format: Enable」をオン
    ・「Solidity: Formatter」はprettier
    ・「Python > Formatting: Provider」はblack
  2. 左側のExplorerアイコンから、ワークスペースに設定したいフォルダを選択する。
  3. Ganacheを開き、QUICKSTARTを選択する。設定からNetwork IDを1337に変更する。
  4. PowerShellを管理者として開き、Set-ExecutionPolicy RemoteSignedを実行し、yを入力する。
  5. Visual Studio CodeのTerminalからpipxをインストールする。
pip install eth-brownie

# エラーが出た場合は以下を実行する
pip uninstall cytoolz
pip install cython
pip install cytoolz
pip install eth-brownie
  1. TerminalでBrownieを使って空のワークスペースを初期化する
brownie init
  1. Visual Studio CodeのTerminalでYarn関連をインストールする。
npm install --global yarn
npm install -g ganache-cli
yarn global add ganache-cli

Brownieフォルダ構成

  • buildフォルダ: デプロイしたら中身が作成されます
    • contractsフォルダ: コンパイルしたコントラクトを格納しています
    • deploymentsフォルダ: 複数のチェーンにデプロイしたコントラクトを格納してます
    • interfaceフォルダ: 使用したインターフェースを格納してます
  • contractsフォルダ: コンパイル前のsolコントラクト保存先
  • interfacesフォルダ: 使用するインターフェース
  • reportフォルダ: レポート出力
  • scriptsフォルダ: デプロイなどをするpyスクリプト保存先
  • testsフォルダ: テスト用のpyスクリプト保存先
  • .env: Etherscanのトークンなどプライベートなものを入れます
  • brownie-config.yaml: 設定を記載するファイルです(標準値

コントラクトを作成する

contractsフォルダに.solファイルを作成し、中にコントラクトを記載します。

※日本語でコメントアウトを記載しているとエンコーディングエラーが発生します

// SPDX-License-Identifier: MIT

pragma solidity 0.8.13;

contract AccountMapping {
    uint256 accountNumber;

    function store(uint256 _accountNumber) public returns (uint256) {
        accountNumber = _accountNumber;
        return _accountNumber;
    }

    function retrieve() public view returns (uint256) {
        return accountNumber;
    }
}

コントラクトをコンパイルする

コントラクトをコンパイルするには以下のコマンドを実行するだけでよいです。

brownie compile

コンパイル後はbuild > contractsフォルダにコントラクトが作成されています。

アカウントを追加する

秘密鍵を取り込むことでガス代を払うアカウントを追加することができます。

秘密鍵の先頭に0xを付け加えるのを忘れないようにします。

秘密鍵は暗号化され、gitにプッシュされることもないので比較的安全です。

brownie accounts new <アカウント名>

アカウントの一覧を表示し、正常に追加できていることを確認します。

brownie accounts list

アカウント削除する場合は以下のコマンドです。

brownie accounts delete <アカウント名>

デプロイに問題ないかテストする

正常にコントラクトがデプロイできるかGanacheでテストします。

testsフォルダにtestをファイル名の先頭に含む.pyファイルを作成する。

.pyファイルの中にtestを先頭に含む関数を作成する。

from brownie import accounts
from brownie import SimpleStorage


def test_deploy():
    # Arrage
    account = accounts[0]
    # Act
    simple_storage = SimpleStorage.deploy({"from": account})
    expected = 0
    # Assert
    assert simple_storage.retrieve() == expected


def test_store():
    # Arrage
    account = accounts[0]
    simple_storage = SimpleStorage.deploy({"from": account})
    # Act
    expected = 15
    simple_storage.store(expected, {"from": account})
    # Assert
    assert simple_storage.retrieve() == expected

ローカルのGanacheでのみテストしたい関数にはpytest.skipを加えるとよいです。

import pytest

def test_only_owner_can_withdraw():
    if network.show_active() not in LOCAL_NETWORKS:
        pytest.skip("Only for local testing")

特定の行動をするとエラーが起きるべき場合(許可されていないアドレスへの出金など)はpytest.raises(exceptions.VirtualMachineError)を使います。

from brownie import exceptions

def test_error():
    with pytest.raises(exceptions.VirtualMachineError):
        fund_me.withdraw({"from": bad_actor})

以下のコマンドでテストを実行します。

成功したら緑でpassedと表示されます。

brownie test -s

# 特定の関数のみテストする場合は-kで指定
brownie test -sk test_store

# エラーがあった場合、--pdbを加えて実行すると変数などを確認可能
brownie test -s --pdb

コントラクトをデプロイする

scriptsフォルダにdeploy.pyを作成する。

from brownie import accounts, config, network

# import .sol file within contracts folder
from brownie import SimpleStorage


def deploy_simple_storage():
    # get account address
    account = get_account()

    # deploy the contract and get the address it was deployed to
    simple_storage = SimpleStorage.deploy({"from": account})
    print(simple_storage)

    # make a call to the contract
    # a call does not need gas thus no account needed
    stored_value = simple_storage.retrieve()
    print("Stored value is {}".format(stored_value))


def get_account():
    # If using Ganache, get the first account created by Ganache
    if network.show_active() == "development":
        return accounts[0]
    # If using testnet or mainnet, get from account list
    elif network.show_active() == "kovan":
        return accounts.load("kovan-test-account")
    else:
        return accounts[0]


def main():
    deploy_simple_storage()

以下のコマンドでデプロイします。デプロイ先を指定しない場合はCUIのGanacheにデプロイされ、buildフォルダに記録は残りません。開いているGUIのGanacheに自動的に接続する場合は、CUIとポート番号が一致していることを確認してください。

brownie run scripts/deploy.py

Ganacheにデプロイしたコントラクトをbuildフォルダに記録したい場合はネットワークを追加し、そのネットワークを指定してデプロイします。

brownie networks add Ethereum ganache-local host=http://127.0.0.1:8545 chainid=1337

brownie run scripts/deploy_fund_me.py --network ganache-local

テストネットまたはメインネットにデプロイする場合はワークスペースに.envというファイルを作り、infuraのProject IDを入力します。

export WEB3_INFURA_PROJECT_ID = <ネットワークID>

また、ワークスペースにbrownie-config.yamlいうファイルを作り、上記の.envを参照するようにします。

dotenv: .env

デプロイするコマンドでネットワークを指定します。

brownie run scripts/deploy.py --network kovan

# ネットワークの一覧
brownie networks list

テストネットまたはメインネットにデプロイされたらbuild > deploymentsフォルダにデプロイ先のアドレスがネットワークごとに保存されます。

デプロイしたコントラクトを操作する

デプロイ済みのコントラクトを操作するためにscriptsフォルダにread_contract.pyを作成する。基本はデプロイ時と同じですが、コントラクトアドレスはデプロイしたコントラクトからインデックスで指定します(例:[0])。

from brownie import accounts, config, network
from brownie import SimpleStorage


def read_contract():
    # get account address
    account = get_account()

    # first deployment is 0, most recent is -1
    simple_storage = SimpleStorage[0]

    # make a transaction to the contrat
    transaction = simple_storage.store(15, {"from": account})
    # wait 1 blocks
    transaction.wait(1)

    # make a call to check the stored value again
    stored_value = simple_storage.retrieve()
    print("Stored value is {}".format(stored_value))


def get_account():
    # If using Ganache, get the first account created by Ganache
    if network.show_active() == "development":
        return accounts[0]
    # If using testnet or mainnet, get from account list
    elif network.show_active() == "kovan":
        return accounts.load("kovan-test-account")
    else:
        return accounts[0]


def main():
    read_contract()

実行する。

brownie run scripts/read_contract.py --network kovan

応用:Dependenciesを使用する

コンパイルする際に、外部のコントラクトを参照したい場合にはdependenciesを設定します。例えば、価格情報を取得するためにChainlinkのコントラクトを使用する場合は、.solファイルでインポートをしていると思います。

import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";

これをBrownieで使えるようにするためには、brownie-config.yamlに以下を追加します。

dependencies:
  - smartcontractkit/chainlink-brownie-contracts@0.4.0
compiler:
  solc:
    remappings:
      - '@chainlink=smartcontractkit/chainlink-brownie-contracts@0.4.0'

後はいつも通りコンパイルするだけです。エラーが出た場合は、buildフォルダを削除すると直ります。

brownie compile

応用:Etherscanでverifyする

EtherscanでコントラクトをVerifyすると緑色のチェックマークがつき、コントラクトの内容がSolidityのソースコードで表示されたりと操作しやすくなります。

まずはEtherscan.ioにログインし、My Profileを開き、API-KEYを取得します。

.envにAPI-KEYを書き込みます。

export ETHERSCAN_TOKEN=<API-KEY>

デプロイ用の.pypublish_source=Trueのパラメータを追加します。

fund_me = FundMe.deploy({"from": account}, publish_source=True)

後は普通にTerminalでデプロイコマンドを実行するだけです。

brownie run scripts/deplpy_fund_me.py --network kovan

デプロイしたネットワークのEtherscanでアドレスを確認すると「Contract」に緑のチェックマークがついているて、Solidityのソースコードが記載されているはずです。「Read Contract」からはコントラクトにCallできますし、「Write Contract」からはコントラクトに対してトランザクションを送信することができます。

応用:ネットワーク固有のアドレスを区別する

色々な環境でテストすると、環境ごとに異なるパラメータやアドレスが必要になります。

そういった場合は、brownie-config.yamlにアドレスを羅列しておきます。

networks:
  kovan:
    eth_usd_price_feed: '0x9326BFA02ADD2366b30bacB125260Af641031331'
    verify: True
  mainnet-fork-dev:
    eth_usd_price_feed: '0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419'
  ganache-local:
    verify: False
  development:
    verify: False

そして、デプロイ用の.pyで必要なアドレスを取得します。

# Set the price feed address
if network.show() != "development":
    price_feed_address = config["networks"][network.show_active()][
        "eth_usd_price_feed"
    ]

ただし、Ganacheには他人のコントラクトが存在しないため、自分で必要なコントラクトをデプロイ(モック)する必要があります。

contractsフォルダにtestフォルダを作成し、その中に.solファイルを作成し、モックしたいコントラクトをgithubなどからコピペし(例:ChainlinkのMockV3Aggregator.sol)、コンパイルします。

brownie compile

そして、デプロイ用の.pyで前提となるコントラクトをデプロイします。

# Set the price feed address
if network.show() != "development":
    price_feed_address = config["networks"][network.show_active()][
        "eth_usd_price_feed"
    ]
else:
    mock_aggregator = MockV3Aggregator.deploy(
        18, Web3.toWei(2000, "ether"), {"from": account}
    )
    price_feed_address = mock_aggregator.address
    print("Deployed MockV3Aggregator")

応用:メインネットのforkでテスト

メインネットのフォーク(複製)を利用することで、より本番に近い環境でテストが可能になります。メインネットのフォークネットワークmainnet-forkはBrownieに標準で搭載されていますが、偽のアドレスにテスト用の資金を入れる必要があるため、あえてネットワークmainnet-fork-devを追加します。

Terminalで以下を実行します。HTTPはInfuraよりAlchemyの方がエラーが少ないです。

brownie networks add development mainnet-fork-dev cmd=ganache-cli host=http://127.0.0.1 fork='https://eth-mainnet.alchemyapi.io/v2/プロジェクトID' accounts=10 mnemonic=brownie port=8545

テストする時はこのネットワークを指定します。

brownie test -sk test_can_fund_and_withdraw --network mainnet-fork-dev