Skip to content

Latest commit

 

History

History

foundry

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 

Introduction to Foundry

このゼミでは、開発ツールとしてFoundryを使います。 FoundryはParadigmが主導して開発をしているRust製のOSSです。 このページではFoundryの概要とよく使うコマンドの一つであるforge testについて簡単に紹介します。 最後に演習があります。

目次

インストール

ドキュメントの下記ページ(「Using Foundryup」の節)の指示に従ってインストールしてください。

https://book.getfoundry.sh/getting-started/installation#using-foundryup

2023-06-13時点では以下のコマンドを実行することでインストールできます。

curl -L https://foundry.paradigm.xyz | bash
foundryup

Foundryupは、Foundryを使って開発を行うためのツールをまとめてインストールするCLIアプリケーションです。RustにおけるRustupと同じです。

foundryupを実行すると、以下の4つのツール(コマンド)がインストールされます。

  • forge: コントラクトをビルド・テストするツール
  • cast: ブロックチェーンと対話するツール
  • anvil: テストと組み合わせて使いやすいEthereumノード
  • chisel: Solidityのインタラクティブシェル

各コマンドに対して一言で説明しましたが、まだピンとこないかもしれません。 まだわからなくても大丈夫です。使っていくと自然にわかるようになります。

使ってみる

プロジェクトの作成

まずは、作業ディレクトリで以下のコマンドを実行してください。 hello_foundryというディレクトリ(プロジェクト)が作成されます。

forge init hello_foundry

プロジェクトの構成

hello_foundry以下のファイル構成について説明します。 tree -L 2は深さ2までのディレクトリとファイルを表示するコマンドです。

$ tree -L 2
.
├── foundry.toml
├── lib
│   └── forge-std
├── script
│   └── Counter.s.sol
├── src
│   └── Counter.sol
└── test
    └── Counter.t.sol

6 directories, 4 files

まず、srcがデプロイするコントラクトを置くディレクトリです。 Counter.solは、デフォルトで配置されるサンプルコントラクトのファイルです。

testがその名の通り、srcディレクトリのコントラクトをテストするためのコントラクトを置くディレクトリです。

scriptは、オンチェーンと対話するためのコントラクトを置きます。 基本的には、srcディレクトリに置かれたコントラクトをデプロイしたいときに使います。 デプロイして、そのデプロイしたコントラクトにコールをしたり、既にデプロイ済みの別のコントラクトに何かコールをして初期設定をしたり、などです。

libは、src/test/scriptで置かれているコントラクトで利用したいライブラリを配置するディレクトリです。 Foundryではライブラリはサブモジュールとして管理されます。 初期状態では、forge-stdというForgeのためのスタンダードライブラリがインストールされており、テストするときに重宝します。

foundry.tomlはプロジェクトの設定について書かれています。 srcディレクトリの名前を変えたいときや、ブロックチェーンのパラメータを変更したいときなどに使えます。

コントラクトのテスト

コントラクトをテストするためには、以下のコマンドを実行します。

forge test

結果は以下のようになります。

$ forge test
[⠆] Compiling...
[⠰] Compiling 21 files with 0.8.18
[⠘] Solc 0.8.18 finished in 4.05s
Compiler run successful!

Running 2 tests for test/Counter.t.sol:CounterTest
[PASS] testIncrement() (gas: 28334)
[PASS] testSetNumber(uint256) (runs: 256, μ: 27553, ~: 28409)
Test result: ok. 2 passed; 0 failed; finished in 32.06ms

Counter.t.solの2つのテストが実行され、どちらもPASSしているのがわかります。

Counter.t.solの中身は以下です。

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "forge-std/Test.sol";
import "../src/Counter.sol";

contract CounterTest is Test {
    Counter public counter;

    function setUp() public {
        counter = new Counter();
        counter.setNumber(0);
    }

    function testIncrement() public {
        counter.increment();
        assertEq(counter.number(), 1);
    }

    function testSetNumber(uint256 x) public {
        counter.setNumber(x);
        assertEq(counter.number(), x);
    }
}

forge testを実行すると以下のような処理が独立して走るイメージを持ってください。

  • setUp()の実行 -> testIncrement()の実行
  • setUp()の実行 -> testSetNumber(乱数)の実行
  • setUp()の実行 -> testSetNumber(乱数)の実行
  • ...
  • setUp()の実行 -> testSetNumber(乱数)の実行

まず、各test関数の実行ごとにsetUp関数が実行されます。 testSetNumber関数においては引数uint256 xがあるため、fuzzingによるテストが実行されます。 先程のforge testの結果で、[PASS] testSetNumber(uint256) (runs: 256, μ: 27553, ~: 28409)とログが出ていましたが、testSetNumberが256回実行されたということです。

setUpで、srcディレクトリにあるCounterコントラクトが作成され、setNumber(0)が実行されますが、これは要はCounterコントラクトの初期設定であり、その初期設定が終わったら、test関数でテストを行うという流れです。 assertEqは、forge-stdTestコントラクトの関数であり、ここで意図した状態遷移が行われたかのチェックを行っています。

forge test-vオプション

forge testでよく使うオプションとして-vオプションがあります。

verbosityの略で、コントラクトのコールをどれだけ詳細に表示するか、また、その詳細に表示する条件について決めるものです。

-vv,-vvv,-vvvv,-vvvvvの4つのオプションが設定可能です。

Verbosity levels:
- 2: Print logs for all tests
- 3: Print execution traces for failing tests
- 4: Print execution traces for all tests, and setup traces for failing tests
- 5: Print execution and setup traces for all tests

試しにforge test -vvvvvを実行してみてください。以下のような結果が得られます。

$ forge test -vvvvv
[⠔] Compiling...
No files changed, compilation skipped

Running 2 tests for test/Counter.t.sol:CounterTest
[PASS] testIncrement() (gas: 28334)
Traces:
  [106719] CounterTest::setUp() 
    ├─ [49499] → new Counter@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
    │   └─ ← 247 bytes of code
    ├─ [2390] Counter::setNumber(0) 
    │   └─ ← ()
    └─ ← ()

  [28334] CounterTest::testIncrement() 
    ├─ [22340] Counter::increment() 
    │   └─ ← ()
    ├─ [283] Counter::number() [staticcall]
    │   └─ ← 1
    └─ ← ()

[PASS] testSetNumber(uint256) (runs: 256, μ: 27476, ~: 28409)
Traces:
  [106719] CounterTest::setUp() 
    ├─ [49499] → new Counter@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
    │   └─ ← 247 bytes of code
    ├─ [2390] Counter::setNumber(0) 
    │   └─ ← ()
    └─ ← ()

  [28409] CounterTest::testSetNumber(3753) 
    ├─ [22290] Counter::setNumber(3753) 
    │   └─ ← ()
    ├─ [283] Counter::number() [staticcall]
    │   └─ ← 3753
    └─ ← ()

Test result: ok. 2 passed; 0 failed; finished in 20.22ms

各関数がどのようなパラメータで呼ばれたか等、詳細な情報が表示されるので、デバッグによく使います。 特に-vvvは失敗したテストに対してのみトレースが表示されることから使うことが多いです。

演習

演習1: Counterのデクリメントの実装

以下のコマンドを実行して全てのテストがパスするように、challenge-counter/Counter.soldecrement関数を実装してください。

forge test -vvv --match-path course/foundry/challenge-counter/Counter.t.sol

演習2: Fungibleトークンのtransferの実装

以下のコマンドを実行してテストtestTransferがパスするように、Fungibleトークンchallenge-token/Token.soltransfer関数を実装してください。 transferFrom関数とapprove関数はまだ実装しなくても大丈夫です。 transfer関数の挙動は、ERC-20トークンの仕様を参考にしてください。

forge test -vvv --match-path course/foundry/challenge-token/Token.t.sol --match-test testTransfer --no-match-test testTransferFrom

以下の点に気をつけてください。

  • トークンの送金が成功したらtrue、失敗したらfalseを返すのではなくリバートさせてください。

演習3: FungibleトークンのtransferFromapproveの実装

演習2の続きです。 以下のコマンドを実行して全てのテストがパスするように、challenge-token/Token.soltransferFrom関数とapprove関数を実装してください。 各関数の挙動はERC-20トークンの仕様を参考にしてください。

forge test -vvv --match-path course/foundry/challenge-token/Token.t.sol

以下の点に気をつけてください。

  • infinite approvalを実装してください。すなわちallowancetype(uint256).maxに設定された場合、transferFromが実行されてもallowanceを変化させないでください。
  • Alice, Bob, Charlieがいる場合に、CharlieがAliceのトークンをBobへ送金する場合もあります。