環境準備
- Visual Studio Code、Microsoft C++ Build Tools、Python、Ganache、NodeJSがインストールされていなければダウンロードしてインストールする。Microsoft C++ Build Toolsをインストールする際は「C++によるデスクトップ開発」を含めるのを忘れない。
- Visual Studio Codeを開き、Solidityの拡張機能をインストールする。
- Visual Studio CodeのTerminalを開き、必要なパッケージをインストールする。
pip install black
pip install py-solc-x
pip install web3
- Visual Studio Codeの設定で以下の設定をする。
・「Format On Save」をオン
・「JSON > Format: Enable」をオン
・「Solidity: Formatter」はprettier
・「Python > Formatting: Provider」はblack - 左側のExplorerアイコンから、ワークスペースに設定したいフォルダを選択する。
- Ganacheを開き、QUICKSTARTを選択する。設定からNetwork IDを1337に変更する。
- PowerShellを管理者として開き、
Set-ExecutionPolicy RemoteSigned
を実行し、y
を入力する。 - Visual Studio CodeのTerminalからpipxをインストールする。
pip install eth-brownie
# エラーが出た場合は以下を実行する
pip uninstall cytoolz
pip install cython
pip install cytoolz
pip install eth-brownie
- TerminalでBrownieを使って空のワークスペースを初期化する
brownie init
- 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>
デプロイ用の.py
にpublish_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