lesson-2_Solidityでスマートコントラクトを作成しよう
👩💻 コントラクトを作成する
メッセージを管理するスマートコントラクトを作成します。 ここで作成するスマートコントラクトは、後でユースケースに合わせて自由に変更できます。
packages/contract/contracts
ディレクトリの下にMessenger.sol
という名前のファイルを作成します。
Hardhatを使用する場合、ファイル構造は非常に重要ですので、注意する必要があります。ファイル構造が下記のようになっていれば大丈夫です 😊
contract/
└── contracts/
└── Messenger.sol
次に、コードエディタでプロジェクトのコードを開きます。
ここでは、VS Codeの使用をお勧めします。ダウンロードは こちら から。
VS Codeをターミナルから起動する方法は こちら をご覧ください。
- ターミナル上で、
code
コマンドを実行
今後VS Codeを起動するのが一段と楽になるので、ぜひ導入してみてください。
コーディングのサポートツールとして、VS Code上でSolidityの拡張機能をダウンロードすることをお勧めします。
ダウンロードは こちら から。
それでは、これからMessenger.sol
の中身の作成していきます。
Messenger.sol
をVS Codeで開き、下記を入力します。
// Messenger.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "hardhat/console.sol";
contract Messenger {
uint256 public state;
constructor() {
console.log("Here is my first smart contract!");
state = 1;
}
}
コードを詳しくみていきましょう。
// Messenger.sol
// SPDX-License-Identifier: MIT
これは「SPDXライセンス識別子」と呼ばれ、ソフトウェア・ライセンスの種類が一目でわかるようにするための識別子です。
詳細については、こちら を参照してみてください。
// Messenger.sol
pragma solidity ^0.8.17;
これは、コントラクトで使用するSolidityコンパイラのバージョンです。
上記のコードでは、このコントラクトを実行するときはSolidityコンパイラのバージョンが0.8.17
以上1.0.0
未満を使用しそれ以外のものは使用しません、という宣言をしています。
Solidityは Semantic Versioning を採用しているため、バージョン表記の見方は
MAJOR.MINOR.PATCH
となり、MAJOR(一番左の番号)は互換性がない修正・変更がSolidityに加わった場合に変わります。
つまり、0.8.17
から1.0.0
未満までの範囲は修正が加わっても互換性がある(コンパイルが可能)変更なので、^
を先頭につけることで、
その範囲のバージョンの違いは許容するということです。
0.8.17
がhardhat.config.ts
でも記載されていることを確認してください。
もし、hardhat.config.ts
の中に記載されているSolidityのバージョンが0.8.17
でなかった場合は、Messenger.sol
の中身をhardhat.config.ts
に記載されているバージョンに変更しましょう。
// Messenger.sol
import "hardhat/console.sol";
コントラクトを実行す る際、コンソールログをターミナルに出力するためにHardhatのconsole.sol
のファイルをインポートしています。
これは、今後スマートコントラクトのデバッグが発生した場合に、とても役立つツールです。
// Messenger.sol
contract Messenger {
uint256 public state;
constructor() {
console.log("Here is my first smart contract!");
state = 1;
}
}
contract
に続けて定義するコントラクトの名前を記述します。
コントラクトはほかの言語でいうところの「class」のように考えることができます。
classの概念については、こちら を参照してください。
コントラクト内では状態変数のstate
を定義しています。
state
はuint256
型でpublic
というアクセス修飾子をつけています。
uint256
は非常に大きな数を扱うことができる「符号なし整数のデータ型」を意味します。
アクセス修飾子に関しては後のレッスンで扱いますが、
public
の指定により、state
はコントラクトの外部からアクセスできるということだけここでは理解しておきましょう。
次にconstructor
を実装しています。
Messenger
をデプロイすると、constructor
が実行されます。
console.log
の中身がターミナル上に表示され、state
が1に設定されます(uintのデフォルト初期値は0
です)。
🔩 constructor とは
constructor
はオプションの関数で、Messenger
コントラクトの状態変数を初期化するために使用されます。
constructor
に関しては、まず以下の特徴をおさえましょう。
Messenger
コントラクトは1つのconstructor
しか持つことができません。constructor
は、スマートコントラクトの作成時に一度だけ実行され、Messenger
コントラクトの状態を初期化するために使用されます。constructor
が実行された後、コードがブロックチェーンにデプロイされます。
🧪 テストを実装する
最低限のコントラクトを実装したので、今度はテストを書きましょう。
test
ディレクトリの中にMessenger.ts
ファイルを作成しtypescript
を使用してテストを記入します。
これから書くテストコードはフロントエンド側のコードであると考えて、
フロントエンド側からコントラクトの関数を呼び出しテストをしているという認識で行うと各ファイルの対応関係と、後のフロントエンド開発がわかりやすくなると思います。
Messenger.ts
に以下のコードを記述してください。
import { expect } from "chai";
import hre from "hardhat";
describe("Messenger", function () {
it("construct", async function () {
const Messenger = await hre.ethers.getContractFactory("Messenger");
const messenger = await Messenger.deploy();
expect(await messenger.state()).to.equal(1);
});
});
中身を見ていきましょう。
import { expect } from "chai";
import hre from "hardhat";
テストに必要なライブラリをimportしています。
describe("Messenger", function () {
it("construct", async function () {
// テストコード
});
});
it
とdescribe
を使用してテス トを記入していきます。
これらはMocha
というテストフレームワークの機能を利用しています。
Mocha
について詳しくはこちらをご覧ください。
it
の引数にテスト名とテスト関数を渡します。
さらに複数のit
関数をdescribe
の引数(の関数)内に渡すことで、個々のテストを1つのdescribe
でグループ化します。
it("construct", async function () {
const Messenger = await hre.ethers.getContractFactory("Messenger");
const messenger = await Messenger.deploy();
expect(await messenger.state()).to.equal(1);
});
実際のテストコードです。
hre.ethers.getContractFactory('Messenger')
でMessenger
コントラクトとデプロイをサポートするライブラリの連携を行い、
Messenger.deploy()
でデプロイを行っています。
expect
は chai ライブラリの機能で、
以下のように使用することでMessenger
コントラクトのstate
がデプロイ後に1
になっていることを確認しています。
expect(await 関数呼び出し).to.equal(期待する値);
📓:
messenger.state()
の意味public
により公開されたコントラクトの状態変数(今回でいうstate
)は、自動的にgetter
関数がコンパイラにより 作られます。 つまりコントラクトオブジェクト.変数名()
のように実行することで値を読み取ることができます。
✍️:
async function ()
とawait
について Javascript(Typescript も同様に) でコードを書いていると、コードの上から順に実行されなくて困ることがあります。 これを非同期処理に関する問題といいます。解決法の一つとして、ここでは
async
/await
を使用します。これを使うと、
await
が先頭についている処理が終わるまで、関数内の他の処理は行われません。await
を使用する関数はasync
をつける必要があります。つまり、
hre.ethers.getContractFactory('Messenger')
の処理が終わるまで、async function
関数の中に記載されている他の処理は実行されないということです。
💁 hardhatで行うテストに関して詳しくはこちらを参考にしてください。
⭐ テストを実行しましょう
AVAX-Messenger/
直下から次のコマンドを実行しましょう。
yarn test
以下のような表示がされます。 実行したテスト名とそのテストがパスしたことがわかります。 また、コンストラクタの出力結果なども確認できます。
🙋♂️ 質問する
ここまでの作業で何かわからないことがある場合は、Discordの#avalanche
で質問をしてください。
ヘルプをするときのフローが円滑になるので、エラーレポートには下記の3点を記載してください ✨
1. 質問が関連しているセクション番号とレッスン番号
2. 何をしようとしていたか
3. エラー文をコピー&ペースト
4. エラー画面のスクリーンショット
コントラクトの作成とテストまでの流れを知ることができました 🎉 次のレッスンでは、スマートコントラクトに機能を追加していきます。