foundry

【foundry】AnvilとCastの使い方&Dappsと連携してのテスト

今回は、foundryを使用して自作のDappsをローカルホストにデプロイして動作を確認していきます。

合わせてfoundryのAnvilやCast機能を確認していきます。

前回同様ここで使用するテストコードなどは全て下記に保存しています。

https://github.com/eggdragons/how-to-use-foundry

Anvilについて

Anvil機能は、ローカルノードを立ち上げる機能になります。

ローカルノードの立ち上げ

早速、Anvil機能を使ってみましょう!

下記コードを使用すると、

Anvil

127.0.0.1:8545にローカルノードが立ち上がります

合わせて、10個のアカウントが作成されます。

ウォレットの数を増やしたり、ガス代をコントロールしたりと様々なオプションがあります。

ローカルノードにコントラクトをデプロイ

次に、ローカルノードにコントラクトをデプロイしていきましょう!

対象のコントラクトは、src/Counter.solで、コントラクトの内容はこちら

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.17;

contract Counter {
    uint256 public number;

    function setNumber(uint256 newNumber) public {
        number = newNumber;
    }

    function increment() public {
        number++;
    }

    function decrement() public {
        number--;
    }

    function add(uint256 a, uint256 b) public pure returns (uint256) {
        return a + b;
    }

    function setAdd(uint256 a, uint256 b) public {
        number = a + b;
    }
}

非常にシンプルなコントラクトになっています。

では、次のコマンドを入力して、デプロイしていきましょう!

新しいターミナルを立ち上げて下記コマンドを入力してください。

※private-keyには、Anvil機能で立ち上げたWalletのprivate-keyを使用します。

forge script script/Counter.s.sol:CounterScript \
--fork-url http://localhost:8545 \
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
--broadcast

無事、コントラクトが立ち上がったかと思います。

なお、Anvil機能を使用したターミナルを見ると、TransactionのReceiptが表示されていることがわかります。

Castについて

Cast機能は、ブロックチェーンと対話できる機能となっております。

今回は、先ほど立ち上げたローカルノードに対して対話していきます。

Call

まずは、先ほどデプロイしたコントラクトのnumberを取得していきます。

※callの次のアドレスには、コントラクトアドレスを使用します。

cast call 0x5fbdb2315678afecb367f032d93f642f64180aa3 \
"number()(uint256)" \
--rpc-url http://localhost:8545

まだnumberには、何も数字を与えていないので、初期値である0が返ってくると思います。

次に引数が必要なときの操作を行ってみましょう。

cast call 0x5fbdb2315678afecb367f032d93f642f64180aa3 \
"add(uint256,uint256)(uint256)" 1 2 \
--rpc-url http://localhost:8545

Function addに対して引数a,bにそれぞれ1,2が当てがわれます。

そのため、戻り値としては、1 + 2 = 3の3が返されます。

次のケースだと、4 + 6 = 10 の10が返ってきます。

cast call 0x5fbdb2315678afecb367f032d93f642f64180aa3 \
"add(uint256,uint256)(uint256)" 4 6 \
--rpc-url http://localhost:8545

Send

今度は、ブロックチェーンにトランザクションを送ってみましょう。

まずは、numberに値をセットしてみましょう。

トランザクションを送りたいコントラクトアドレスを記入し、その後、トランザクションを送るアドレスを記載します。

最後に、Functionを記載し、ローカルノードに対して実行していきます。

cast send 0x5fbdb2315678afecb367f032d93f642f64180aa3 \
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
"setNumber(uint256)" 5 \
--rpc-url http://localhost:8545

このトランザクションを送ると、Receiptが返ってくると思います。

コントラクトのnumberが書き換わったか確認してみましょう

cast call 0x5fbdb2315678afecb367f032d93f642f64180aa3 \
"number()(uint256)" \
--rpc-url http://localhost:8545

先ほど書き換えた5が返ってくると思います。

最後に、引数を使用してトランザクションを送ってみます。これは、Callと一緒になります。

cast send 0x5fbdb2315678afecb367f032d93f642f64180aa3 \
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
"setAdd(uint256,uint256)" 1 2 \
--rpc-url http://localhost:8545

numberにa+bの値をセットする関数を呼び出しているため、numberに1+2=3の3がセットされました。

コントラクトのnumberが書き換わったか確認してみましょう

cast call 0x5fbdb2315678afecb367f032d93f642f64180aa3 \
"number()(uint256)" \
--rpc-url http://localhost:8545

3が返ってくると思います。

Dappsと連携してのテスト

最後に、Dappsと連携してのテストを行なっていきます。

下準備

まずは、必要なパッケージをインストールします。

ルートフォルダで、npm iを実行してください。

次に、counter-app/src/utils/anvil.tsを必要に応じて修正します。

// Anvilコマンドより取得
export const address = "0x5fbdb2315678afecb367f032d93f642f64180aa3";
export const PRIVATE_KEY =
  "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
export const localhost = "http://localhost:8545";

Dappsを操作

下準備が終わったら、npm startでDappsを立ち上げていきましょう

最低限の実装しかしていないので、見た目はご容赦ください。

表示されているコードは、コントラクトのコードになります。

かなり雑な実装になっていますが、コントラクトとdappsとの通信はこのように行われています。

export const useHandleContract = (inputs: Inputs) => {
  const [data, setData] = useState<number>();

  const provider = new ethers.providers.JsonRpcProvider(localhost);
  const wallet = new ethers.Wallet(PRIVATE_KEY, provider);

  const readContract = async (target: string, arg: number[]) => {
    const contract = new Contract(address, Abi.abi, provider);
    try {
      const value = await contract[target](...arg);
      setData(Number(value));
    } catch (error) {}
  };

  const writeContract = async (target: string, arg: number[]) => {
    const contract = new Contract(address, Abi.abi, wallet);
    try {
      const tx = await contract[target](...arg);
      const receipt = await tx.wait();
      console.log(receipt);
    } catch (error) {
    } finally {
      await readContract("number", []);
    }
  };

  const read = async () => {
    await readContract("number", []);
  };

  const setNumber = async () => {
    await writeContract("setNumber", [inputs.a]);
  };

  const increment = async () => {
    await writeContract("increment", []);
  };

  const decrement = async () => {
    await writeContract("decrement", []);
  };

  const add = async () => {
    await readContract("add", [inputs.a, inputs.b]);
  };

  const setAdd = async () => {
    await writeContract("setAdd", [inputs.a, inputs.b]);
  };

  return [
    data,
    {
      read,
      setNumber,
      increment,
      decrement,
      add,
      setAdd,
    },
  ] as const;
};

詳しくは、このdappsのコードcounter-app/src/を参照してみてくださいね

readボタンは、numberがreturnされます。

その他のボタンは、コントラクトのfunctionと連動しています。

aやbに値を入れて、Current Number〜が書き換わることをチェックしてみてくださいね。

参考に、一回操作してみましょう。

aに5、bに10を入力して「add」を押してみてください。

すると、戻り値である 15がCurrent Number〜に表示されると思います。

add functionは、pure関数となっておりブロックチェーンに書き込みがおこなわれません。

そのため、ここで「read」を押すとCurrent Number〜には、ブロックチェーンに保存されている数字が表示されます。

Anvil機能を使用したターミナルを見ると、TransactionのReceiptが表示されていることがわかります。

eth_sendRawTransaction

    Transaction: 0x3386255be170d97a94c1392eecf6c6dcc44bf0124aaeb2631f6351239bc12e21
    Gas used: 26382

    Block Number: 9
    Block Hash: 0x48d65e18fd2cbf13a875eeea24a0473ecf684024ca330358dd9ac3f04d8871e2
    Block Time: "Wed, 08 Feb 2023 05:23:32 +0000"

最後に

多くの人にfoundryを使ってもらい、テストコードの充実とさらなる進化の恩恵を望んでいます。

そのため、foundryの基本的な使い方について、複数回にわたり紹介してきました。

storageの解析など、まだ未対応の部分も多くありますが、foundryの魅力が少しでも伝わっていると嬉しいです。

-foundry