メインコンテンツまでスキップ

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.17hardhat.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を定義しています。 stateuint256型で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 () {
// テストコード
});
});

itdescribeを使用してテストを記入していきます。 これらは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()でデプロイを行っています。

expectchai ライブラリの機能で、 以下のように使用することで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. エラー画面のスクリーンショット

コントラクトの作成とテストまでの流れを知ることができました 🎉 次のレッスンでは、スマートコントラクトに機能を追加していきます。