day16までの知識を総動員して、一つのコントラクトを実装します。ここではクラウドファンディングの最小機能を持つコントラクトを例にします。
まずは、コントラクトを開発する際の流れを理解するために開発プロセスについて解説し、その後に実際のコードを紹介します。
開発プロセス
これまで解説した内容は文法が中心でしたが、これは開発工程の中のコーディング(プログラミング)の部分を担っているに過ぎません。そもそもコーディングをする前に、どういうものを作るか、どのように実現するかを決めていく必要があります。web3の開発に限らず、一般的なシステム開発においては、以下の工程を踏みます。
作ろうとしているコントラクトの要件を定義します。どういう人がこのコントラクトを使うか、どういう使い方をするか、といったことを想定し、これらを実現するための機能を決めます。
今回の例であるクラウドファンディングの場合、以下の機能が必要です。
- 出資したい人がこのコントラクトに送金すると、アドレスと金額が記録される
- このコントラクトの所有者(出資を受ける人)は出資された総額を引き出すことができる
- 誰でもオーナーや出資者、出資額の情報を閲覧することができる
要件定義で決めた機能を持つコントラクトを設計します。必要なデータ構造、関数、イベント、修飾子などを定義していきます。
クラウドファンディングの例の場合、上記の1〜3が関数として定義され、そこで使われる各データ(出資者アドレス・金額の情報、所有者情報等)はコントラクト内の変数として定義する必要がありそうです。
設計した内容をもとに実際にコントラクトを実装します(コーディング、プログラミング)。
Remixの場合、実際にデプロイしたコントラクトをRemixのUIから実行することでテストする事が可能です。後述のhardhatなどのフレームワークを使う場合、テスト用のコードを作成することでテスト実施を行うことが可能になります(コード化することによって、テストの自動化=テスト工程の省力化、を図ることができ、特に繰り返し同じプログラムを改修するような場合に非常に有効です)。
ローカルの環境で確認できたコードはテストネットに実際にデプロイ(プログラムを配置すること)し、そこで確認を行います。テストネットでの確認結果に問題がなければ、メインネットにデプロイし、遂に本番稼働となるわけです。
コーディング
上記のプロセスを経て、実際に実装したコードの例が次のものになります。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
error Funding__NotOwner();
error Funding__NotEnoughETH();
/**
* @title A contract for crowdfunding
* @author simochan
* @notice This contract is to demo a sample funding contract
*/
contract Funding {
// State variables
address[] private s_funders;
mapping(address => uint256) private s_addressToAmountFunded;
address private immutable i_owner;
// Event
event Funded(address indexed funder, uint256 indexed fundedAmount);
// Modifiers
modifier onlyOwner() {
if (msg.sender != i_owner) {
revert Funding__NotOwner();
}
_;
}
constructor() {
i_owner = msg.sender;
}
function fund() public payable {
if(msg.value <= 100000000000000000) {
revert Funding__NotEnoughETH();
}
s_funders.push(msg.sender);
s_addressToAmountFunded[msg.sender] += msg.value;
emit Funded(msg.sender, msg.value);
}
function withdraw() public onlyOwner {
for (uint256 i = 0; i < s_funders.length; i++) {
address funder = s_funders[i];
s_addressToAmountFunded[funder] = 0;
}
s_funders = new address[](0);
(bool success, ) = i_owner.call{value: address(this).balance}("");
require(success);
}
function getOwner() public view returns (address) {
return i_owner;
}
function getFunder(uint256 index) public view returns (address) {
return s_funders[index];
}
function getAddressToAmountFunded(address funder) public view returns(uint256) {
return s_addressToAmountFunded[funder];
}
}
テスト
hardhatを用いたテストコードの作成とテストコードによるテスト実行は、のちの章で詳述します。ここでは、Remix(のローカルネットワーク)にデプロイしたコントラクトの手動テストで試してみます。
具体的には、以下のようないくつかのケースを想定し、これらがうまく動作するかを確認します。
- デプロイ後、
getOwner
によりオーナー情報がデプロイしたアドレスと同じであるかを確認する - オーナー以外の複数のアドレスから0.1ETHを超える金額の
fund
(出資)を実行し、コントラクトのETH残高が増えていることを確認する(仮にアドレス1とアドレス2の2つのアドレスから1.0ETHずつ試すものとする)- また、定義したイベント
Funded
(出資イベント)がコンソール上記録されることを確認する
- また、定義したイベント
- 引数0および1として、
getFunder
(出資者のアドレス取得)を実行し、2で実行したアドレスが返却されることを確認する - 3で返却されたアドレスを引数として、
getAddressToAmountFunded
(出資額取得)を実行し、出資額が返却されることを確認する - 0.1ETH未満の金額の
fund
を実行し、revertされ、コンソール上にエラーが表示されることを確認する - オーナーが
withdraw
(引き出し)を実行し、コントラクトのETH残高が0になっていることを確認する
ご意見をお聞かせください!
この記事、または、web3チュートリアル全体について、是非、あなたのご意見をお聞かせください。
アンケートはこちらからご回答いただけます。
無料相談承ります
オンラインでの無料相談を承っています。ご希望の方は、お問い合わせフォームよりご連絡ください。
ITの専門家があなたのご質問にお答えいたします。
Comments