Brownie とは
モチベーション etc
- brownie ベースのプロジェクトのコードを読む必要が出てきたため(過去 hardhat のみ)
- まずは公式ドキュメントから
- (ほぼ英語->日本語にするだけ)
Overview
- EVM をターゲットにしたスマートコントラクトの開発フレームワーク
- Python ベース
Features
- サポートしている言語
- Solidity, Vyper
- 同一プロジェクトで、上記が共存できる様子
- Solidity, Vyper
- include (Unit?) testing module
- pytest によるテスト
- coverage 評価も含む
- pytest によるテスト
- include Property-based testing module
- hypothesis
- debugging tools
- built-in console
- Support for ethPM packages
GETTING STARTED
Installing Brownie
Dependencies
- python3
- ganache-cli
Using Brownie with Hardhat
- Ganache の代わりに、Hardhat network を使用することができる
Creating a New Project
- 新規プロジェクトの作成方法は2パターン
-
brownie init
... empty project -
brownie bake
... use template-
Brownie Mixes にあるものをテンプレートとして扱える
- Defi 利用のためのテンプレートや、フロント込みのテンプレートもある(ex. React)
-
Brownie Mixes にあるものをテンプレートとして扱える
-
Structure of a Project
|- contracts // Contract sources (Solidity, Vyper)
|- interfaces // Interface sources (Solidity, Vyper)
|- scripts // Scripts for deployment and interaction (Python)
|- tests // Scripts for testing the project (Python)
| // ↓ auto generated (should not edit or delete)
|- build // compiler artifacts & unit test results etc
L reports // JSON report files for use in the GUI
Brownie Package Manager
- Brownie では他プロジェクトをパッケージとしてインストールできる
brownie pm
Installing a Package
- Github, ethPM をサポートしている
- Github
-
contracts/
フォルダが必要brownie pm install OpenZeppelin/openzeppelin-contracts@3.0.0 brownie pm install aragon/aragonos@4.0.0
-
- ethPM
- Ethereum Package Manager
- EVMのスマートコントラクトやプロジェクトを配布するために使用される分散型パッケージマネージャー
- ethPM パッケージは、abi, ソースコード, bytecode, デプロイデータなどを含むJSONオブジェクト
- 利用するには、パッケージ名と利用可能なレジストリのアドレスを知っている必要がある
ethpm://[CONTRACT_ADDRESS]:[CHAIN_ID]/[PACKAGE_NAME]@[VERSION]
Using Packages in your Project
- Importing Sources from a Package
import "OpenZeppelin/openzeppelin-contracts@3.0.0/contracts/math/SafeMath.sol";
- Using Packages in Tests
def test_with_compound_token(pm): compound = pm('defi.snakecharmers.eth/compound@1.1.0').CToken
- Declaring Project Dependencies
- 以下のようにプロジェクト構成ファイル
brownie-config.yaml
に依存関係を記述する必要がある
dependencies: - aragon/aragonOS@4.0.0 - defi.snakecharmers.eth/compound@1.1.0
- 以下のようにプロジェクト構成ファイル
The Brownie GUI
- Brownie はテストカバレッジデータを閲覧したり、コントラクトの compile 済の bytecode を解析したりするためのGUIが用意されている
Getting Started
# from terminal
brownie gui
# from console
>>> Gui()
CORE FUNCTIONALITY
Working with Accounts
-
Accounts
コンテナを使用すると、全ての local account にアクセスできる-
Account.balance
... アカウントの残高を確認する -
Account.transfer
... アカウント間で ethereum を送信し、他の簡単な取引を行うために使用される -
Account.add
... 新しいアカウントをランダムに生成する
-
Working with Contracts
-
ContractContainer
オブジェクト- Brownie がロードされるたびにプロジェクトは自動的にコンパイルされ、デプロイ可能なコントラクトごとにオブジェクトが作成される
- 個々のデプロイされたコントラクトにアクセスするために使われるコンテナ
- また、新しいコントラクトをデプロイする際にも使用される
ContractContainer.deploy
- また、新しいコントラクトをデプロイする際にも使用される
Interacting with your Contracts
- コントラクトがデプロイされると、
calls
やtransactions
を介してコントラクトと interact することができる - transactions
- ネットワークにブロードキャストされ、ブロックチェーンに記録される
- 実行には ether がかかり、ブロックチェーンの状態を変更することができる
- calls
- トランザクションをブロードキャストせずに、ネットワーク上でコードを実行するために使用される
- ブロックチェーンの状態を変更することはできない
- calls は通常 getter method を使用して Contract から storage の値を取得するために使用される
Transactions
- state を変更する contract method は
ContractTx
オブジェクトを介して呼び出される- このオブジェクトは transactions を実行し、
TransferReceipt
を返す
- このオブジェクトは transactions を実行し、
Calls
- state を変更しない contract method は
ContractCall
オブジェクトを介して呼び出される- このオブジェクトは transaction をブロードキャストせずに contract method を呼び出す
- transaction を介してメソッドにアクセスする場合は、
ContractCall.transact
を使用できる
Contracts Outside of your Project
- live network または forked network で作業する場合は、すでにデプロイされているコントラクトと interact するための
Contract
オブジェクトを生成できる -
Contract
オブジェクトを作成する方法-
interface/
フォルダ内の interface から生成する - block explorer や ethPM registry などの remote source から情報を取得し生成する
- ABIからインスタンス化
-
Contract.from_abi
classmethod を使用する
-
-
- Fetching from a Remote Source
- etherscan に query を発行してオブジェクトを作成する場合は、
Contract.from_explorer
を使用する
- etherscan に query を発行してオブジェクトを作成する場合は、
- Persisting Contracts between Sessions
- Contract オブジェクトの作成に使用されるデータは local database に保存され、セッション間で永続化される
- クラスメソッドによる作成後、address で Contract を初期化することにより、オブジェクトの再作成ができる
- Contract オブジェクトの作成に使用されるデータは local database に保存され、セッション間で永続化される
Interacting with the Blockchain
- block 情報へのアクセス
-
Chain
オブジェクト
-
- transaction データへのアクセス
- local transaction 履歴
-
TxHistory
コンテナが利用可能- Brownie セッション中にブロードキャストされた全ての transaction を保持する
- Other Transactions
-
TransactionReceipt
オブジェクトを取得するために、chain.get_transaction
を利用できる
-
-
- local transaction 履歴
Manipulating the Development Chain
- Brownie は ganache-cli を local の開発環境として使用するように設計されている
- マイニング、スナップショット、タイムトラベルなどの機能は
Chain
オブジェクトを介してアクセスできる
- マイニング、スナップショット、タイムトラベルなどの機能は
- Mining New Blocks ...
chain.mine
- Time Travel ...
chain.time
,chain.sleep
- Snapshots ...
chain.snapshot
,chain.revert
,chain.reset
- Undo / Redo ...
chain.undo
,chain.redo
- 直近のトランザクションを前方/後方に移動できる
- test debug の際に便利
- 直近のトランザクションを前方/後方に移動できる
Data Types
- Brownie は custom data type を使用して、一般的な値の操作を簡単にする
- Wei
- 値が Ether の量を表す場合に使用される
- int のサブクラス
- 値が Ether の量を表す場合に使用される
- Fixed
- Vyper の10進数値を扱うために使用される
- decimal.Decimal のサブクラス
TESTING
Writing Unit Tests
- Brownie は Unit test に pytest framework を利用している
Getting Started
- Test File Structure
- 配置箇所 ...
test/
folder 配下 - ファイル名 ...
test_*.py
or*_test.py
- テスト対象となるメソッド
- class の外の function && prefix が
test
の function
- class の外の function && prefix が
from brownie import accounts def test_account_balance(): balance = accounts[0].balance() accounts[0].transfer(accounts[1], "10 ether", gas_price=0) assert balance - "10 ether" == accounts[0].balance()
- 配置箇所 ...
Fixtures
- 各テストの実行前に呼び出し、テストに必要な初期条件を設定する
@pytest.fixture
- brownie でのテストのための fixtures も用意されている
Handling Reverted Transactions
- テストを実行すると revert するための transaction で
VirtualMachineError
例外が発生する- ↑の assersion を行うために、
brownie.reverts
が提供されている
- ↑の assersion を行うために、
Developer Revert Comments
- 開発者用の revert comment を設定できる
-
// dev: ...
or# dev: ...
- revert message が設定されていなかった場合
-
tx.revert_msg
... dev revert message が返却される
-
- revert message が既に設定されている場合
-
tx.revert_msg
... 元々の contract で定義されている revert message が返却される -
tx. dev_revert_msg
... dev revert message が返却される
-
-
Parametrizing Tests
-
@pytest.mark.parametrize
で Parametrizing Tests を利用可能にする
Testing against Other Projects
- brownie の
pm
fixture は Brownie の package manager でインストールされたパッケージへのアクセスを提供する-
pm
を利用することで、自分のプロジェクトと外部プロジェクトとの間のやりとりを検証するテストケースを書くことができる- 引数に ProjectId を渡す -> コントラクトのオブジェクトが返却される
@pytest.fixture(scope="module") def compound(pm, accounts): ctoken = pm('defi.snakecharmers.eth/compound@1.1.0').CToken yield ctoken.deploy({'from': accounts[0]})
-
Running Tests
$ brownie test # 全テスト実行
$ brownie test tests/test_transfer.py # 指定したファイルのテスト実行
$ brownie test --update # 更新があったテストのみ実行 (一回全部実行後)
$ brownie test --interactive # デバッグ
$ brownie test --gas # ガス利用料の評価
$ brownie test --coverage # カバレッジの評価
$ brownie test -n auto # (pytest-xdist プラグインを利用した)並列実行
Property-Based Testing
- hypothesis フレームワークを利用した Property-Based Testing
What is Property-Based Testing?
- コンセプト
- 単一のシナリオのためのテストを書くのではなく、様々なシナリオを記述するテストを書き、自分で一つ一つ手書きするのではなく、コンピュータにその可能性を探らせるということ
- 基本的なプロセス
- テストするスマートコントラクトの機能を選択
- 関数の入力範囲を指定して、常に同じ結果が得られるように設定する
- (↑の設定による)仕様に基づいて、ランダムなデータを使用して関数をテストする
- 結果に対するアサーション
Writing Tests
- 2つのメソッドを利用する
from brownie.test import given, strategy
(利用方法なので以下省略)
Stateful Testing
- 複雑なシステムをテストするために利用される Property-Based Testing より高度な方法
- Stateful Testing では、組み合わせることができるアクションをいくつか定義して、失敗になるアクションのユースケースを発見する
Coverage Evaluation
How Coverage Evaluation Works
- ソースコードの statement と branch に関連する opcodes の map を生成し、各トランザクションの stack trace を解析して、どの opcodes が実行されたかを確認する
Security Analysis with MythX
- Brownie は MythX analysis API と統合されていて、プロジェクトの自動セキュリティスキャンを可能にする
- MythX ... 静的解析、動的解析、シンボリック実行を使用してプロジェクトの脆弱性をスキャンするスマートコントラクトセキュリティサービス
NETWORKS AND DEVELOPMENT
Deployment Basics
(brownie だけじゃないけど一応記録対象に)
- Brownie を使用して、deployment が可能
- ブロックチェーンは永久不変のため、一度 deploy されるとそのスマートコントラクトにおいては変更できない
- メインネットにデプロイする前に、推奨する流れ
- deployment script の作成
- ローカルのネットワークを対象に、script をテスト
- public test network で script をテスト
- mainnet に deploy
- デプロイされたら、ethPM パッケージを作成し、プロジェクトと連携したいところとの連携を簡単にできる
- メインネットにデプロイする前に、推奨する流れ
Writing a Deployment Script
- deployment script は Brownie script と同じように機能するが、非ローカルネットワーク用における注意点
- デプロイする前にローカルアカウントのロックを解除する必要がある
- Gas を払う必要があり、値が設定されていない場合は Brownie が gas price と gas limit を自動で計算する
- 場合によっては手動で設定することを推奨する
Running your Deployment Script
-
--network
で network を選択するbrownie run deploy.py --network ropsten
The Deployment Map
- Brownie は
build/deployment
folder にmap.json
ファイルを保持し、live network に deploy された全ての Contract を chain と contract name でソートしてリストアップする
{
"1": {
"SolidityStorage": [
"0x73B74F5f1d1f7A00d8c33bFbD09744eD90220D12",
"0x189a7fBB0038D4b55Bd03840be0B0a38De034089"
],
"VyperStorage": [
"0xF104A50668c3b1026E8f9B0d9D404faF8E42e642"
]
}
}
- 各 Contract のリストは deployment の block number でソートされ、最も新しい deployment が最初に表示される
Interacting with Deployed Contracts
- Brownie は live network 上での Contract の deployment に関する情報を保存する
- Contract が deploy されると生成された ProjectContract instance は今後の Brownie セッションで利用可能になる
Verifying Deployment Source Code
- Brownie は etherscan がサポートする全てのネットワークにおいて、Solidity Contract のソースコード検証を自動的に行う機能がある
- Contract をデプロイしながら検証する場合は
publish_source=True
の引数を追加する
- Contract をデプロイしながら検証する場合は
- デプロイ時とコンパイラの設定を同じにすれば既に deploy された Contract の検証も可能
token = Token.at("0x114A107C1931de1d5023594B14fc19d077FC4dfD")
Token.publish_source(token)
Saving Deployments on Development Networks
- development network 上での deployment 成果物が必要な場合は、
brownie-config.yaml
でdev_deployment_artifacts: true
にする
Network Management
- Brownie は development, live network 両方で利用できる
- development 環境では、local の network として Ganache を使用している
Network Configuration
- CLIを利用して処理する
brownie networks
# 既存のネットワークの表示
$ brownie networks list
Brownie - Python development framework for Ethereum
The following networks are declared:
Ethereum
├─Mainnet (Infura): mainnet
├─Ropsten (Infura): ropsten
├─Rinkeby (Infura): rinkeby
├─Goerli (Infura): goerli
└─Kovan (Infura): kovan
Ethereum Classic
├─Mainnet: etc
└─Kotti: kotti
Development
├─Ganache-CLI: development
└─Ganache-CLI (Mainnet Fork): mainnet-fork
# 新しいネットワークの追加
brownie networks add [environment] [id] host=[host] [KEY=VALUE, ...]
Using a Hosted Node / Providers
- Alchemy や Infura などのサービスは ethereum node への public access を提供する
- 独自に実行するよりも簡単なオプションだが、いくつか制限がある
- 一部のRPCエンドポイントは使用できない可能性がある
- Infura は debug_traceTransaction メソッドへのアクセスを提供しない
- そのため、Infura を使用すると、Brownie のデバッグツールは機能しない
- Infura は debug_traceTransaction メソッドへのアクセスを提供しない
- ホストされたノードはアカウントへのアクセスを提供しない
- transaction 実行前に自分のローカルアカウントのロックを手動で解除する必要がある
- 一部のRPCエンドポイントは使用できない可能性がある
- 独自に実行するよりも簡単なオプションだが、いくつか制限がある
Using a Forked Development Network
- Ganache を使用すると、live network から fork して開発ネットワークを作成できる
- プロジェクトとメインネットにデプロイされている他のプロジェクトとの間の相互作用をテストするために役立つ
-
Contract.from_explorer
を使用して、source を取得し、fork したネットワーク上の Contract と対話可能
Native EVM-Compatible Chain Integrations
- Brownie は EVM互換のチェーンを native support している
- Ethereum Classic
- Binance Smart Chain
- Fantom Opera
- Polygon Network
- XDai Network
- EVM互換チェーンの native support を有効にするには、2つの要件がある
- public にアクセス可能なJSON-RPCエンドポイント
- Contract source と ABI を fetch するための APIサポートのある block explorer
Account Management
- Infura などのホストされたノードを介して remote network に接続する場合、Accounts コンテナは空になる
- transaction を実行する前に、local account を brownie に追加する必要がある
- local account は keystores という暗号化された JSON ファイルに保存される
$ brownie accounts generate <id> # 新規アカウントの作成
$ brownie accounts new <id> # 秘密鍵からの import
$ brownie accounts import <id> <path> # Keystore からの import
$ brownie accounts export <id> <path> # Keystore の export
Unlocking Accounts
- script または console から local account にアクセスするためには、最初に lock を解除する必要がある
Accounts.load
- account のロックが解除されると、Accounts コンテナ内で利用可能になる
Signing Raw Text Messages (EIP-191)
- EIP-191 メッセージに署名するには
LocalAccount.sign_defunct_message
を使用して eth_account SignableMessage オブジェクトを生成する
Signing Messages
- EIP712Message に署名するためには、eth_account SignableMessage オブジェクトを生成するために、
LocalAccount.sign_message
を利用する
Using a Hardware Wallet
- Brownie は Clef を介してハードウェアウォレットを利用できる
- Clef ... Geth に含まれるアカウント管理ツール
Brownie を使用しているプロジェクト
vyper を使用しているプロジェクト