Aleph Zero ドキュメント翻訳
【BUILD】
『Aleph Zero smart contracts basics
(Aleph Zero スマートコントラクトの基本)』
開発者の皆さんを、Aleph Zero Testnetでのスマートコントラクトの機能探求にご招待します。このガイドでは、始め方を説明します!
この簡単なチュートリアルでは、Aleph Zero Testnetにおけるスマートコントラクトの基本機能について説明します。
次のページでは、Testnetアカウントの設定方法、スマートコントラクト開発のためのコンピュータの準備方法、スマートコントラクトの記述、コンパイル、デプロイの方法、そしてデプロイしたスマートコントラクトとのやり取り方法を順を追って説明します。
注意:スマートコントラクトは現在、Aleph Zeroのメインネット上で稼働しています。
『Testnetアカウントの設定』
Testnetを活用するために必要な情報はすべてこちらで確認できます。このテスト環境を利用するために必要な2つの重要なコンポーネントについて説明します。
Aleph Zero Testnetアカウントを設定するために必要なコンポーネントは以下の2つです:
Testnetウォレット/エクスプローラー
アカウントを作成し、スマートコントラクトのデプロイや操作を行うために使用します。Testnet Faucet
Aleph Zero Testnet上で操作を行い、スマートコントラクトの開発を試す際に必要な無料のTZEROトークンを受け取ることができます。
手順は簡単です:
Testnetウォレットの「アカウント」タブを開き、画面の指示に従ってアカウントを追加します。
Testnet Faucetを使って、新しく作成したアカウントにコインを受け取ります。アカウントアドレスをコピーし、Faucetに貼り付けるだけです。TZEROトークンはほぼ瞬時に入手可能です。ウォレットのエクスプローラータブに戻って確認するよりも早いでしょう :)
『必要なツールのインストール』
Aleph Zeroで最初のスマートコントラクトを実行する前に、Rustとink!を使った開発のためにコンピュータを準備する必要があります。このガイドで始め方を確認しましょう。
最初に行うべきことは、Rustとink!での開発に向けてコンピュータを準備することです。
〈Rust〉
このガイドでは、Rustツールチェーンを管理するために https://rustup.rs インストーラーと"rustup"ツールを使用します。Rustをインストールするデフォルトの方法であり、推奨されています。ただし、他の方法を希望する場合は、公式Rustウェブサイトの「その他のインストール方法」のセクションを参照してください。
"rustup"をインストールおよび設定するには、次のコマンドをシェルに入力します:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/env
これで、コンピュータに最新の安定版Rustがインストールされます。スマートコントラクトの開発には、少し新しい"nightly"バージョンといくつかの追加コンポーネントが必要です:
rustup toolchain install nightly
rustup component add rust-src --toolchain nightly
rustup target add wasm32-unknown-unknown --toolchain nightly
Rustにはネイティブなパッケージマネージャーである"cargo"が付属しており、Rustコードのコンパイルにも使用されます。"cargo"が正しくインストールされており、シェル環境で使用可能であることを確認してください:
cargo --help
〈ink!〉
"ink!"は、RustでWASMスマートコントラクトを書くための組み込みドメイン固有言語(EDSL)です。簡単に言えば、"ink!"はRustに「アドオン」を加えることで、通常のバイナリコードではなく、Substrateベースのスマートコントラクト実行環境で互換性のある特別なWASMコードを生成します。最終的に、"ink!"スマートコントラクトはわずかに追加された"ink!"固有のヘッダー(「マクロ」と呼ばれる)を持つ通常のRustプログラムです。
"ink!"を使い始めるには、まずコントラクトのWebAssemblyバイトコードを最適化するために使用される"binaryen"パッケージをインストールする必要があります。"binaryen"はおそらく、デフォルトのパッケージマネージャーからインストールできます:
#UbuntuまたはDebianユーザー向け:
sudo apt install binaryen
#MacOSユーザー向け:
brew install binaryen
#ArchまたはManjaroユーザー向け:
pacman -S binaryen
または、直接バイナリリリースをダウンロードすることも可能です。
"binaryen"がインストールされた状態で、"cargo contract"をインストールします:
cargo install --force --locked cargo-contract
"cargo contract"は、スマートコントラクト開発に便利なコマンドを追加する"cargo"のアドオンです。次のコマンドで使用可能な機能を確認できます:
cargo contract --help
これを使用して、最初の"ink!"スマートコントラクトを作成します。
『最初のコントラクトを作成する』
開発環境の準備が整ったので、最初のスマートコントラクトを作成しましょう。このチュートリアルでは、ERC20トークンの簡略版を開発します。
ℹ️マークのあるボックスには、Rustプログラミング言語の基本情報が記載されています。Rustに詳しい方はスキップして構いません。
📍マークのあるボックスには、"ink!"によるスマートコントラクト開発に関する一般的な注意点が記載されています。これらの情報は、このチュートリアルを完了する上で必須ではありませんが、後で役立つ可能性があります。
〈導入〉
必要なツールがすべて揃ったので、最初のink!スマートコントラクトを開発する準備が整いました!
このチュートリアルで作成するコントラクトは、ERC20トークンの非常に簡略化されたバージョンです。このコントラクトは、インスタンス化されると新しい種類の代替可能トークンのプールを作成し、それをアカウント間で転送できるようにします。コントラクトはアカウントとその残高のレジストリを保持し、残高を照会しトークンを転送するメソッドを提供します。
📍:ここで作成するトークンは、チェーンのネイティブ通貨とは無関係です!そのため、チェーンの内部メカニズムはトランザクションの正確性を保証しません。ロジックが適切であることを確認するのは、コントラクトの作成者であるあなたの責任です。
〈コントラクトの作成〉
まず、"cargo contract"を使ってコントラクトテンプレートを生成しましょう:
cargo contract new mytoken
cd mytoken
このコマンドにより、次のファイルが含まれる新しいディレクトリ"mytoken"が作成されます:
"lib.rs":コントラクトのコードが含まれるRustソースファイル
"Cargo.toml":"cargo"にコントラクトのビルド方法を説明するマニフェストファイル(このチュートリアルでは変更の必要はありません)
".gitignore":コントラクトをバージョン管理するために"git"を使用する場合の設定ファイル
編集するのは"lib.rs"のみです。残りの2つのファイルはそのままで構いません。"lib.rs"を開くと、最も簡単な「hello-world」コントラクト、つまりflipper(単一のブール値を保持し、それを反転させることができるもの)が記載されています。このコードを見てみることをお勧めしますが、理解できない部分があっても心配しないでください。一歩ずつコードを変更しながらすべてを説明します。
〈実装〉
ink!で記述されたスマートコントラクトは、実際にはink!マクロ("#[ink...]"のような行)を使用する通常のRustコードです。これらのマクロの役割は、通常のプログラムではなく、Aleph Zeroブロックチェーンにデプロイ可能なWASMスマートコントラクトを生成するようコンパイルプロセスを変更することです。
ファイルの先頭には、次の構成マクロを含める必要があります:
#![cfg_attr(not(feature = "std"), no_std, no_main)]
この見た目が怖いマクロは、コンパイラに標準ライブラリを含めないよう指示します(ink!はスマートコントラクト開発に適した独自のプリミティブセットを使用します)。また、"main"シンボルの出力を無効にすることも許可します。
続いて、メインのink!マクロでモジュールを定義します:
#[ink::contract]
mod mytoken {
//...
}
このマクロは、"mytoken"モジュールがスマートコントラクトの定義であり、ink!がそのモジュール内のさまざまなコンポーネントを調べるべきであることをink!に伝えます。また、"Balance"や"Account"といった便利な型エイリアスを提供します。
さらに、現在は気にする必要のないいくつかの不変条件を強制します:
コントラクトには、"#[ink::storage]"が付けられた構造体が1つだけ必要です。
コントラクトには、少なくとも1つの"#[ink::constructor]"が付けられた関数が必要です。
ℹ️:Rustのモジュールは、型や関数を1つの大きなスコープにまとめたコレクションと考えることができます。
---ストレージ---
コントラクトの最初のコンポーネントはストレージです。ストレージには、ブロックチェーン上に保存されるデータが含まれ、コントラクトの状態を保持します。このチュートリアルでは、ユーザーとそのトークン保有数のマッピング、および新しいトークンの総供給量を保持します。このデータは、対応するink!ストレージマクロを付けた1つのRust "struct"に囲まれます:
#[ink::contract]
mod mytoken {
use ink::storage::Mapping;
#[ink(storage)]
#[derive(Default)]
pub struct Mytoken {
total_supply: Balance,
balances: Mapping<AccountId, Balance>,
}
}
ℹ️:ここでは、次のようにインスタンス化されるRust "struct"を宣言しています:
let my_token = Mytoken {
total_supply: somevalue1,
balances: somevalue2,
};
そして、そのフィールドには次のようにアクセスできます:
my_token.total_supply
また、次のように書く代わりに:
Mytoken { total_supply: total_supply, balances: balances }
以下のような簡略記法が利用できます(コード内に"total_supply"と"balances"という変数が存在する場合):
Mytoken { total_supply, balances }
ここでは、"ink::storage"クレートが提供する"Mapping"データ構造を使用しています。ink!スマートコントラクトを記述する際には、Rust標準ライブラリのデータ構造を使用することはできません。しかし、幸いなことにink!はチェーン上で保存するために最適化されたキーと値のマップという便利な代替手段を提供しています。
📍:この構造体に保存するデータを選ぶ際は慎重になる必要があります。なぜなら、ストレージの使用には料金が発生するためです(Aleph Zeroの場合、料金は非常に小さいですが、それでも割り当てる内容を把握しておく価値があります)。たとえば、アドレスと残高のマッピングを保存するのは適切です。しかし、コントラクトが画像を扱う場合、それらをオフチェーンに保存し、ハッシュだけをコントラクトのストレージに保存することを検討するべきです。
また、ink!に"Default"トレイトの実装を生成するよう指示している点にも注目してください。今回の場合、この設定により、コンパイラが"total_supply"フィールドを0("Balance"型のデフォルト値)に初期化し、"balances"を空の"Mapping"(対応する"Default"実装)として初期化できるようになります。
---コンストラクタ---
次のステップは、コントラクトのコンストラクタを実装することです。このコンストラクタは、新しく定義した"struct Mytoken"の"impl"ブロック内に配置し、対応するink!マクロを付ける必要があります。
📍:コントラクトは、"#[ink(constructor)]"マクロが付けられている限り、任意の数(ゼロ以外)のコンストラクタを持つことができます。
#[ink::contract]
mod mytoken {
// ... (storage definition)
impl Mytoken {
#[ink(constructor)]
pub fn new(total_supply: Balance) -> Self {
let mut balances = Mapping::default();
let caller = Self::env().caller();
balances.insert(caller, &total_supply);
Self {
total_supply,
balances,
}
}
}
}
このコンストラクタは1つの引数、新しく作成したトークンの初期供給量を受け取ります。そして、その全供給量をコントラクト作成者(コンストラクタを呼び出したアカウント)のアカウントに預けます。
このコンストラクタは、通常のRust "struct"のコンストラクタと非常に似ています。最初に"Default::default()"関数を呼び出して空の"Mapping"を作成し、その後に最初のエントリを挿入し、供給量全体をコンストラクタの呼び出し元に割り当てています。
"Self::env().caller()"メソッドを使用することで、コントラクトを呼び出したアカウントのアドレスにアクセスできます。コンストラクタの文脈では、これはコントラクトの作成者/所有者となります。
ℹ️:"impl"ブロックには、同じ名前の構造体(今回の場合は"Mytoken")で操作するメソッドが含まれます。他の言語では、これらのメソッドをクラス/構造体本体に書くことがありますが、Rustでは定義と実装を分離することで柔軟性が向上します。
なお、Rustでは1行コメントをダブルスラッシュ"//"で記述します。
---メッセージ---
上記のコントラクトコンストラクタが、ink!マクロを付けた通常のRustコンストラクタであるように、コントラクトの呼び出し可能なメソッド(ink!では「メッセージ」と呼ばれる)もまた、別のink!マクロで注釈を付けた通常のRustメソッドです。
#[ink::contract]
mod mytoken {
// ... (storage definition)
impl Mytoken {
// ... (constructor definition)
#[ink(message)]
pub fn total_supply(&self) -> Balance {
self.total_supply
}
#[ink(message)]
pub fn balance_of(&self, account: AccountId) -> Balance {
self.balances.get(&account).unwrap_or_default()
}
}
}
📍:"#[ink(message)]"でマークされたメソッドは公開("pub fn")として宣言する必要があります。
ここでは、コントラクトのストレージにアクセスする2つのメソッドを定義しています:トークンの総供給量を読み取るtotal_supplyメソッドと、特定のアカウントが保持するトークン数を読み取るbalance_ofメソッドです。これらのメソッドは読み取り専用で、コントラクトストレージを変更しません。そのため、ブロックチェーンにトランザクションを送信せずに呼び出すことが可能です。
"balance_of"メソッドは、最初に指定されたアカウントに対して"balances"マッピングから値を取得します。結果は"Option"構造体でラップされて返されますが、"unwrap_or_default()"メソッドを使用して、実際の値またはデフォルト値("Balance"型の場合は便利なことに0)を取得します。
ℹ️:メソッドが呼び出された構造体インスタンスにアクセスするには、"self"キーワードを使用します(一部の言語では"this"キーワードが同様の意味を持ちます)。
Rustの関数は、"fn"キーワードで宣言され、続いて関数名、引数リスト("arg: Type"の形式)、および矢印->の後に戻り値の型が記述されます。最後の行をそのまま返す場合、"return"キーワードを使う必要はありません。
注意:コードは、短絡的な"return"ステートメントを過剰に使用しない方が、よりエレガントになる場合があります。
"pub"キーワードは、関数を公開(宣言されたモジュールの外部からアクセス可能)としてマークします。
---エラー---
まず、トークン"transfer"の機能を見る前に、Rustにおけるエラー処理の慣例的な方法を理解する必要があります。いくつかの言語とは異なり、Rustは例外の概念を採用せず、代わりに代数的エラー処理を使用します。失敗する可能性のあるメソッドは"Result<T, E>"型を持ちます。これは次のように定義されています:
pub enum Result<T, E> {
Ok(val: T), // T is the expected type of the computation
Err(msg: E), // E is an error type of your choice
}
ℹ️:Rustのジェネリック型に慣れていない場合、ここで"Result"は型パラメータ"T"と"E"を持つ型(基本的には型のプレースホルダー)です。コード内で"Result"を任意の型"T"と"E"でインスタンス化できます。例えば、"Result<u128, str>"では、"val"は128ビットの符号なし整数で、"msg"は文字列になります。ただし、1回インスタンス化したらその型"T"と"E"に固執する必要がありますが、インスタンスを作成する場所では好きな型を選ぶことができます(例を見ると分かりやすくなります)。
カスタムエラーの定義
コントラクトでは、"Result"の"Err"部分として使用するカスタム"Error"列挙型を定義します。
#[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub enum Error {
InsufficientBalance,
}
この入門チュートリアルでは、怖そうなマクロについては心配しないでください😊 後のチュートリアルで詳しく説明します。今のところは、そのままコピーすれば十分です。以下のトークン"transfer"の実装例では、この方法を実際に使用する例を確認できます。
ℹ️:Rustの"enum"(列挙)型に慣れていない場合、ここでは引数を取らない1つのバリアント(またはコンストラクタ)を持つ"Error"型を定義しています。このエラーをインスタンス化する際は"Error::InsufficientBalance"と記述し、その型は"Error"になります(例えば、関数の型シグネチャで指定する必要がある場合など)。
トークン転送メソッド
最後に、アカウント間でトークンを転送するメソッドを実装します。
mod mytoken {
// ...
impl Mytoken {
// ... constructor definition
// ... error definition
#[ink(message)]
pub fn transfer(&mut self, to: AccountId, value: Balance) -> Result<(), Error> {
let from = self.env().caller();
let from_balance = self.balance_of(from);
if from_balance < value {
return Err(Error::InsufficientBalance);
}
self.balances.insert(from, &(from_balance - value));
let to_balance = self.balance_of(to);
self.balances.insert(to, &(to_balance + value));
Ok(())
}
}
}
このメソッドは、任意のユーザーが自分のトークンの一部を選択した受取人に転送するために呼び出すことができます。もしユーザーが所有している以上のトークンを転送しようとすると、このメソッドは何の変更も行わずに終了します。なお、"transfer"メソッドの中では、以前定義した"balance_of"メソッドを使用しています。
以前のメソッドとの最も重要な違いは、"transfer"メソッドがコントラクトのストレージを変更するという点です。この事実は、最初の引数として"&self"ではなく"&mut self"を使用することで示す必要があります。この要件はコンパイラによって強制されるため、もし"mut"を忘れた場合は、コントラクトがビルドされず、コンパイラが"mut"を使用するように提案してくれます。そのため、バグのあるコントラクトをデプロイしてしまう心配はありません。
ℹ️:また、以前説明した"enum"(列挙)型に基づいて、戻り値の型が"Result<(), Error>"であることに注目してください。
"Ok"バリアントには"()"が含まれています。これはC(++)でいうところの"void"に相当し、簡単に言えば「何も値を返さない」という意味です。
"Err"バリアントには、事前に定義した"Error"型を含める必要があります。そのため、返す際には"Err(Error::InsufficientBalance)"のようにインスタンス化します。
ℹ️:このチュートリアルの目的のために、参照("&")はC++のような言語と非常によく似ていると考えて構いません。値をコピーしたり引き渡したりする代わりに、他の関数に「一時的に貸し出す」だけです。後に見るように、この貸し借りの直感的な仕組みはRust言語で非常によく形式化されています。
借り手が一時的に取得した値に変更を加える可能性があることを示すには、参照に"&mut"を付ける必要があります。
幸いなことに、前述のようにRustのコンパイラはこの分野の問題を解決するための非常に役立つアドバイスを提供してくれます。そのため、コンパイルが正しく行われるコントラクトを作成するのに十分な支援が得られるでしょう。このトピックについてさらに詳しく知りたい場合は、The Rust Bookを参照してください。
---テスト---
他のプログラムと同様に、スマートコントラクトもテストする必要があります。この開発プロセスの部分は、通常のRustプログラムで行う方法と非常に似ています。テストはオフチェーンで実行され、ink!はコントラクトが将来的に稼働するオンチェーン環境をシミュレートするための便利なツールをいくつか提供しています。
ここでは、実装された各メソッドの基本的な動作確認(サニティチェック)を行う、非常に簡単なテストスイートを示します。以下のコードは、コントラクトと同じ"lib.rs"ファイル内に配置してください。
#[cfg(test)]
mod tests {
use super::*;
#[ink::test]
fn total_supply_works() {
let mytoken = Mytoken::new(100);
assert_eq!(mytoken.total_supply(), 100);
}
#[ink::test]
fn balance_of_works() {
let mytoken = Mytoken::new(100);
let accounts =
ink::env::test::default_accounts::<ink::env::DefaultEnvironment>();
assert_eq!(mytoken.balance_of(accounts.alice), 100);
assert_eq!(mytoken.balance_of(accounts.bob), 0);
}
#[ink::test]
fn transfer_works() {
let mut mytoken = Mytoken::new(100);
let accounts =
ink::env::test::default_accounts::<ink::env::DefaultEnvironment>();
assert_eq!(mytoken.balance_of(accounts.bob), 0);
assert_eq!(mytoken.transfer(accounts.bob, 10), Ok(()));
assert_eq!(mytoken.balance_of(accounts.bob), 10);
}
}
このコードの詳細には触れませんが、上記のコントラクトを実装した後であれば、内容は自明であるはずです。
テストスイートの実行方法
テストスイートは、"mytoken"フォルダ内でターミナルから"cargo test"を実行することで実行できます。
cargo test
Compiling mytoken v0.1.0 (/home/user/ink/mytoken)
Finished test [unoptimized + debuginfo] target(s) in 1.08s
Running unittests lib.rs (target/debug/deps/mytoken-668aad4b5e4b8a01)
running 3 tests
test tests::balance_of_works ... ok
test tests::total_supply_works ... ok
test tests::transfer_works ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
📍:ink!のバージョン4.0では、スマートコントラクトの開発フローにエンドツーエンドのテストが導入されました。これは、コントラクトの正確性を確保するための推奨される追加手法として、次回のチュートリアルで取り上げます。
---まとめ---
最後に、コントラクトのすべての要素を組み合わせて、最終バージョンを完成させましょう。また、各要素が何をするのかを説明するドキュメントコメント("///...")を追加することもできます。ここでは簡潔さのために省略していますが、これを含めることを推奨します。この情報は、Contracts UIを介してコントラクトとやり取りするユーザーに表示されます。
mytoken/lib.rs
#![cfg_attr(not(feature = "std"), no_std, no_main)]
#[ink::contract]
mod mytoken {
use ink::storage::Mapping;
#[ink(storage)]
#[derive(Default)]
pub struct Mytoken {
total_supply: Balance,
balances: Mapping<AccountId, Balance>,
}
#[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub enum Error {
InsufficientBalance,
}
impl Mytoken {
#[ink(constructor)]
pub fn new(total_supply: Balance) -> Self {
let mut balances = Mapping::default();
let caller = Self::env().caller();
balances.insert(caller, &total_supply);
Self {
total_supply,
balances,
}
}
#[ink(message)]
pub fn total_supply(&self) -> Balance {
self.total_supply
}
#[ink(message)]
pub fn balance_of(&self, owner: AccountId) -> Balance {
self.balances.get(owner).unwrap_or_default()
}
#[ink(message)]
pub fn transfer(&mut self, to: AccountId, value: Balance) -> Result<(), Error> {
let from = self.env().caller();
let from_balance = self.balance_of(from);
if from_balance < value {
return Err(Error::InsufficientBalance);
}
self.balances.insert(from, &(from_balance - value));
let to_balance = self.balance_of(to);
self.balances.insert(to, &(to_balance + value));
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[ink::test]
fn total_supply_works() {
let mytoken = Mytoken::new(100);
assert_eq!(mytoken.total_supply(), 100);
}
#[ink::test]
fn balance_of_works() {
let mytoken = Mytoken::new(100);
let accounts =
ink::env::test::default_accounts::<ink::env::DefaultEnvironment>();
assert_eq!(mytoken.balance_of(accounts.alice), 100);
assert_eq!(mytoken.balance_of(accounts.bob), 0);
}
#[ink::test]
fn transfer_works() {
let mut mytoken = Mytoken::new(100);
let accounts =
ink::env::test::default_accounts::<ink::env::DefaultEnvironment>();
assert_eq!(mytoken.balance_of(accounts.bob), 0);
assert_eq!(mytoken.transfer(accounts.bob, 10), Ok(()));
assert_eq!(mytoken.balance_of(accounts.bob), 10);
}
}
}
〈コンパイル〉
次に、コントラクトをビルドします。
cargo +nightly contract build --release
結果のファイルはmytoken/target/ink/フォルダ内に配置されます。コンパイルが成功すると、以下の3つのファイルが生成されます。
"mytoken.wasm": コンパイルされたコントラクトのバイナリWASMファイル
"metadata.json": コントラクトのABI(アプリケーションバイナリインターフェース)を含むファイル
"mytoken.contract": 上記の2つをまとめたもので、チェーンエクスプローラーでの操作を簡単にするためのもの
これで、"mytoken"コントラクトをAleph Zero Testnetにデプロイする準備が整いました!
『Aleph Zeroテストネットへの
コントラクトのデプロイ』
このチュートリアルでは、Aleph Zeroブロックチェーン上のスマートコントラクト環境とやり取りする方法をいくつか紹介します。
ここでは、新しいスマートコントラクトをAleph Zeroテストネットにデプロイします。Aleph Zeroブロックチェーン上のスマートコントラクト環境とやり取りする方法はいくつかありますが、ここではそのうちの2つを紹介します。Contracts UIを使用する方法と、コマンドラインを使用して"cargo contract"ツールを利用する方法です。
〈Contracts UI〉
コントラクトのデプロイ
スマートコントラクトをデプロイし、操作するために必要なすべてのツールは、便利な形でContracts UIにパッケージ化されています。開発者向けWebウォレットを使用してアカウントを作成し、ファウセットから無料のTZEROを取得しておくことを確認してください。そうしないと、ウォレット内の一部のタブやボタンが表示されない可能性があります。
「Developer」タブに移動し、ポップアップメニューから「Contracts」をクリックしてスマートコントラクト用のContracts UIにアクセスしてください(または、上記のリンクを直接たどることもできます)。
Contracts UIに入ったら、サイドメニューで「Add new contract」をクリックしてデプロイメントページに移動し、「Upload new contract」を選択してください。
デプロイに使用したいアカウントを選択します(複数のアカウントがある場合)。このアカウントは、"mytoken"コントラクトによって作成される新しいトークンの全初期供給量を保持することになります。次に、「Upload contract bundle」フィールドをクリックして、以前に"cargo contract"で生成した"mytoken.contract"ファイルの場所を選択してください(通常、"mytoken/target/ink/"フォルダに保存されています)。すべてが正常であれば、「Valid contract bundle!」というテキストが表示されます。
「Next」をクリックしてください。次に、コントラクトをインスタンス化するためのコンストラクタに渡すパラメータを入力するよう求められます。この場合、これはトークンの初期供給量のみです(任意の数値を選択できます。これはアカウントのTZERO残高とは一切関係ありません)。
カスタムオプションについては、後のチュートリアルで取り上げる予定です。右側のコスト見積もりに注意してください。これには、アカウントがこのコントラクトを作成するための十分な残高を持っているかどうか(つまり、ガス料金を賄えるか)が表示されます。
すべてが順調であれば、次の画面が表示されます:
残りは「Upload and instantiate」ボタンをクリックしてコントラクトをデプロイするだけです!選択したアカウントマネージャー(現在のデフォルトはPolkadot.js拡張機能)を使用してトランザクションに署名する必要があります。その後、コントラクトに関する情報が表示された画面と、コントラクトで呼び出すメソッドを選択するためのドロップダウンリストが表示されます。
また、「Metadata」タブを選択して、コントラクトで呼び出せるメソッドを表示することもできます。
---コントラクトとのやり取り---
いよいよ新しいトークンで遊ぶ時が来ました!左のコントラクトリストから「mytoken」を選択した状態で、コントラクト上で呼び出すメソッドを選択するためのドロップダウンリストが表示されます。
"balanceOf"や"totalSupply"のような読み取り専用メソッドは、右側の「Outcome」モーダルに結果が即座に返されることに気付くでしょう。これは、読み取り専用メソッドでは呼び出しのためにトランザクションを作成する必要がないためです。
注目すべきは"transfer"メソッドを呼び出す部分です。
転送の有効な受信者を得るためには、2つ目のアカウントが必要です(実際には、アカウントアドレスが正しい形式であれば、既存のアカウントに関連付けられている必要はありません。しかし、テスト目的で有効なアドレスを取得する最も簡単な方法は、別のアカウントを作成することです)。
次に、転送する金額を入力する必要があります(コントラクト作成時に選択した初期供給量よりも大きな値を入力すると何が起こるかを試すことができます)。追加のオプションについては、再びデフォルト値のままにしておきます。
コントラクトのインスタンス化と同様に、画面右側に便利なガス見積もりが表示されます。これを使用して、この呼び出しを実行するのに十分な資金(ガス料金的に)があるかどうかを確認できます。そして再びトランザクションに署名すれば、転送が完了します!"balanceOf"メソッドを使用して、転送が実際に行われたかどうかを確認することができます。
〈コマンドライン〉
---コントラクトのデプロイ---
Aleph Zeroブロックチェーン上でスマートコントラクトと、より自動化されたプログラム可能な方法でやり取りしたい場合は、コントラクトのコンパイルに使用した"cargo contract"コマンドラインツールを使用して、上記のすべての操作を実行することができます。"cargo contract"に関連するすべてのextrinsics機能の簡単な概要はこちらにあります。
実際のチェーンとやり取りする"cargo contract"のサブコマンドはすべて、チェーンのエンドポイントアドレスとユーザーの秘密鍵(シードフレーズ)を定義するフラグと共に呼び出す必要があります。このセクションでのコマンドを簡潔にするために、まずこれらのフラグで使用する値を環境変数として定義しましょう。
export SEED="[put your 12 words seed phrase here]"
export URL="wss://ws.test.azero.dev"
新しいコントラクトのデプロイは、"instantiate"サブコマンドを使用して行うことができます。コントラクトが保存されている"mytoken"フォルダにいることを確認し、以下のコマンドを実行してください:
cargo contract instantiate --suri "$SEED" --url "$URL"
--constructor new_token
--args 1000
このコマンドの出力には、デプロイトランザクションの結果としてチェーンが生成したさまざまなイベント(手数料の支払い、コントラクトアカウントの作成など)が含まれます。最後のイベントは"System ➜ ExtrinsicSuccess"で、デプロイが成功したことを示します。その後、作成したコントラクトのアドレスに関する情報が表示されます。このアドレスを環境変数に保存して、コントラクトとのやり取りをより便利にしましょう:
export CONTRACT="5GNruCfnGXjSPkW7LkRnB45MiHJJtvc6NBKZnDSnFh4So3ws
ℹ️:このコントラクトアドレスを使用して、前のセクションで使用したWebウォレットに既存のコントラクトをインポートすることもできます。スマートコントラクトマネージャーで「Add an existing contract」をクリックし、アドレスを貼り付けてください。また、コンパイル時に生成されたABIを含む"metadata.json"ファイル("mytoken/target/ink/"フォルダにあるはず)をアップロードする必要があります。その後、以前説明した方法と同様にコントラクトとやり取りできます。
---コントラクトとのやり取り---
Webウォレットセクションで述べたように、スマートコントラクトで実行できる呼び出しには2種類あります。
状態クエリ:コントラクトの状態を変更せずに問い合わせるもので、トランザクションを必要としません。
実行可能な呼び出し:状態を変更するもので、署名済みトランザクションを送信し、手数料を支払う必要があります。
これら2種類のアクションは、"cargo contract"では"--dry-run"フラグで区別されます。
まずは、引数のない状態クエリを実行して、トークンの総供給量を確認してみましょう:
cargo contract call --suri "$SEED" --url "$URL"
--contract "$CONTRACT"
--message total_supply
--dry-run
出力には、"total_supply"関数から返されるデータなどが含まれます:
Result Success!
Reverted false
Data 1000
Gas Consumed 248300975
Gas Required 6815744000
Storage Deposit StorageDeposit::Charge(0)
引数を伴う状態クエリ(例:"balance_of")を実行するには、"--args"フラグを追加します。コントラクトのデプロイやコンストラクタの呼び出しと同様です:
cargo contract call --suri "$SEED" --url "$URL"
--contract "$CONTRACT"
--message balance_of
--args 5FWmHxBXH4WfrryA6xdbaQRJALJ549aL11HMyybqDy5iNRtE
--dry-run
Output:
Result Success!
Reverted false
Data 1000
Gas Consumed 322051074
Gas Required 6815744000
Storage Deposit StorageDeposit::Charge(0)
ここでの引数は有効なアカウントアドレスであれば何でもかまいません。ただし、この時点では、コントラクト作成者("$SEED"に関連付けられたアドレス)以外のすべてのアカウントのトークン残高は0で、作成者が総供給量である1000トークンを保持しています。次に、他のアカウントにトークンを転送してみましょう:
cargo contract call --suri "$SEED" --url "$URL"
--contract "$CONTRACT"
--message transfer
--args 5D853t8wQuHJpfWvtcB3VUyKo8Ki44HQwgTmynGT4i5UVhbr 100
今回はチェーンの状態を変更するトランザクションを送信したいため、
"--dry-run"フラグは省略する必要があります。出力には、トランザクションによって生成されたイベントのリストが含まれ、最後におなじみの
"System ➜ ExtrinsicSuccess"が表示され、呼び出しが成功したことを示します。これで100トークンの転送が実際に行われたことを確認できます。
cargo contract call --suri "$SEED" --url "$URL" \
--contract "$CONTRACT" \
--message balance_of \
--args 5FWmHxBXH4WfrryA6xdbaQRJALJ549aL11HMyybqDy5iNRtE \
--dry-run
Output:
Result Success!
Reverted false
Data 900
Gas Consumed 322051074
Gas Required 6815744000
Storage Deposit StorageDeposit::Charge(0)
cargo contract call --suri "$SEED" --url "$URL" \
--contract "$CONTRACT" \
--message balance_of \
--args 5D853t8wQuHJpfWvtcB3VUyKo8Ki44HQwgTmynGT4i5UVhbr \
--dry-run
Output:
Result Success!
Reverted false
Data 100
Gas Consumed 322051074
Gas Required 6815744000
Storage Deposit StorageDeposit::Charge(0)
〈次のステップ〉
おめでとうございます!これであなたはスマートコントラクト開発者です。ink!スマートコントラクトについてさらに学びたい場合は、優れたink!ドキュメントをぜひご覧ください。また、こちらにあるink!のサンプルコントラクト集をチェックすることもできます。問題や質問がある場合は、alephzero.orgに記載されているチャンネルのいずれかを通じてお気軽にお問い合わせください。
『コントラクトの拡張』
ここでは、ink!でスマートコントラクトを書く際に役立つさまざまなトピックに関する「ハウツー」のコレクションを紹介します。
基本から中級までのトピックや構文を網羅したサンプルスマートコントラクトを用意しました。これを参考にして、自分のアイデアを形にする際に役立ててください。詳細は「Bulletin Board example contract」をご覧ください。
コントラクトの"README.md"をスタート地点として活用してください。
ハッキングを楽しんでください!