Open7

Brownie とは

linnefromicelinnefromice

モチベーション etc

  • brownie ベースのプロジェクトのコードを読む必要が出てきたため(過去 hardhat のみ)
  • まずは公式ドキュメントから
    • (ほぼ英語->日本語にするだけ)
linnefromicelinnefromice

https://eth-brownie.readthedocs.io/en/stable/

Overview

  • EVM をターゲットにしたスマートコントラクトの開発フレームワーク
    • Python ベース

Features

  • サポートしている言語
    • Solidity, Vyper
      • 同一プロジェクトで、上記が共存できる様子
  • include (Unit?) testing module
    • pytest によるテスト
      • coverage 評価も含む
  • include Property-based testing module
    • hypothesis
  • debugging tools
  • built-in console
  • Support for ethPM packages
linnefromicelinnefromice

GETTING STARTED

Installing Brownie

Dependencies

  • python3
  • ganache-cli

Using Brownie with Hardhat

  • Ganache の代わりに、Hardhat network を使用することができる

Creating a New Project

  • 新規プロジェクトの作成方法は2パターン
    1. brownie init ... empty project
    2. brownie bake ... use template
      • Brownie Mixes にあるものをテンプレートとして扱える
        • Defi 利用のためのテンプレートや、フロント込みのテンプレートもある(ex. React)

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()
linnefromicelinnefromice

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

  • コントラクトがデプロイされると、callstransactionsを介してコントラクトと interact することができる
  • transactions
    • ネットワークにブロードキャストされ、ブロックチェーンに記録される
    • 実行には ether がかかり、ブロックチェーンの状態を変更することができる
  • calls
    • トランザクションをブロードキャストせずに、ネットワーク上でコードを実行するために使用される
    • ブロックチェーンの状態を変更することはできない
      • calls は通常 getter method を使用して Contract から storage の値を取得するために使用される

Transactions

  • state を変更する contract method はContractTxオブジェクトを介して呼び出される
    • このオブジェクトは transactions を実行し、TransferReceiptを返す

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を使用する
  • Persisting Contracts between Sessions
    • Contract オブジェクトの作成に使用されるデータは local database に保存され、セッション間で永続化される
      • クラスメソッドによる作成後、address で Contract を初期化することにより、オブジェクトの再作成ができる

Interacting with the Blockchain

  • block 情報へのアクセス
    • Chainオブジェクト
  • transaction データへのアクセス
    • local transaction 履歴
      • TxHistoryコンテナが利用可能
        • Brownie セッション中にブロードキャストされた全ての transaction を保持する
      • Other Transactions
        • TransactionReceiptオブジェクトを取得するために、chain.get_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 のサブクラス
  • Fixed
    • Vyper の10進数値を扱うために使用される
    • decimal.Decimal のサブクラス
linnefromicelinnefromice

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

Handling Reverted Transactions

  • テストを実行すると revert するための transaction で VirtualMachineError例外が発生する
    • ↑の assersion を行うために、brownie.revertsが提供されている

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?

  • コンセプト
    • 単一のシナリオのためのテストを書くのではなく、様々なシナリオを記述するテストを書き、自分で一つ一つ手書きするのではなく、コンピュータにその可能性を探らせるということ
  • 基本的なプロセス
    1. テストするスマートコントラクトの機能を選択
    2. 関数の入力範囲を指定して、常に同じ結果が得られるように設定する
    3. (↑の設定による)仕様に基づいて、ランダムなデータを使用して関数をテストする
    4. 結果に対するアサーション

Writing Tests

  • 2つのメソッドを利用する
from brownie.test import given, strategy

(利用方法なので以下省略)

Stateful Testing

  • 複雑なシステムをテストするために利用される Property-Based Testing より高度な方法
  • Stateful Testing では、組み合わせることができるアクションをいくつか定義して、失敗になるアクションのユースケースを発見する

Coverage Evaluation

How Coverage Evaluation Works

Security Analysis with MythX

  • Brownie は MythX analysis API と統合されていて、プロジェクトの自動セキュリティスキャンを可能にする
    • MythX ... 静的解析、動的解析、シンボリック実行を使用してプロジェクトの脆弱性をスキャンするスマートコントラクトセキュリティサービス
linnefromicelinnefromice

NETWORKS AND DEVELOPMENT

Deployment Basics

(brownie だけじゃないけど一応記録対象に)

  • Brownie を使用して、deployment が可能
  • ブロックチェーンは永久不変のため、一度 deploy されるとそのスマートコントラクトにおいては変更できない
    • メインネットにデプロイする前に、推奨する流れ
      1. deployment script の作成
      2. ローカルのネットワークを対象に、script をテスト
      3. public test network で script をテスト
      4. mainnet に deploy
    • デプロイされたら、ethPM パッケージを作成し、プロジェクトと連携したいところとの連携を簡単にできる

Writing a Deployment Script

  • deployment script は Brownie script と同じように機能するが、非ローカルネットワーク用における注意点
    1. デプロイする前にローカルアカウントのロックを解除する必要がある
    2. 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の引数を追加する
  • デプロイ時とコンパイラの設定を同じにすれば既に deploy された Contract の検証も可能
token = Token.at("0x114A107C1931de1d5023594B14fc19d077FC4dfD")
Token.publish_source(token)

Saving Deployments on Development Networks

  • development network 上での deployment 成果物が必要な場合は、brownie-config.yamldev_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 を提供する
    • 独自に実行するよりも簡単なオプションだが、いくつか制限がある
      1. 一部のRPCエンドポイントは使用できない可能性がある
        • Infura は debug_traceTransaction メソッドへのアクセスを提供しない
          • そのため、Infura を使用すると、Brownie のデバッグツールは機能しない
      2. ホストされたノードはアカウントへのアクセスを提供しない
        • transaction 実行前に自分のローカルアカウントのロックを手動で解除する必要がある

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 に含まれるアカウント管理ツール