今回は、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の魅力が少しでも伝わっていると嬉しいです。