こんにちは。なまはげです。
最近まで蜂窩織炎という足がめちゃくちゃ腫れ上がる病気だったのですが、抗生剤をアホみたいに飲みまくったところ無事完治し、現在は腫れが引いた自分の脚の美脚っぷりにうっとりしてます。
前回はEnigmaのインストールとEnigmaを利用したDapps(なんかいい名称ないんですかね、E-Dappとかどうでしょう?)の概観を説明しました。
今回はこれから作っていくアプリの説明とシークレットコントラクトについて説明します。
アプリの全体像はこちらに載っています。まだ不完全な部分もあるみたいですが、とりあえず頑張って実装していきましょう。
今回のアプリの概要
今回作るアプリはくじ引きをして勝った人にERC721(NFT)を送るものです。
処理の流れは以下の通りです。
- くじ引きの主催者はくじ引きの登録を行う。
- 商品となるNFTをEthereumのスマートコントラクトに送付する
- 参加者は参加登録をする
- 主催者はくじ引きを開始してあたりを決める
- あたりをゲットした人にNFTを送る
という流れです。
今回はシークレットコントラクトで2~4を実装していきます。
実は「シークレットコントラクト」についての記事はこれが初めてです!
フォルダを用意する
まずはシークレットコントラクトを書くフォルダを用意しましょう。
$ cd secret_contracts
$ cargo new lottery --lib
これで完了です。--libとつけているのはEnigmaはシークレットコントラクトをライブラリとして動かすからです。
これでrustを動かす環境が整いました。
最初からsecret_contracts内にあるフォルダは消しちゃってください。
次にCargo.tomlを編集します。これは利用するライブラリの情報やrustを動かす際の設定をするファイルです。
Cargo.tomlを開いたら次のように書き込みましょう。
[package]
name = "contract"
version = "1.0.0"
[dependencies]
eng-wasm = "0.1.3"
eng-wasm-derive = "0.1.3"
serde = "1.0.84"
serde_derive = "1.0.98"
rustc-hex = "2.0.1"
[lib]
crate-type = ["cdylib"]
[profile.release]
panic = "abort"
lto = true
opt-level = "z"
以下のように書き加えましょう。
depenenciesが利用するライブラリの一覧です。この辺はふーんくらいに思っておきましょう。
[lib]のcrate-type
は今回ライブラリとして実装するのですが、そのライブラリのタイプを指定するものです。
cdylib
というのは外部から別の言語に組み込まれる動的ライブラリとしてコンパイルするためのもの。
基本的にシークレットコントラクトはRust環境内から呼び出すものではないので全てcdylib
指定で良さそうです。(また、サイズも小さめらしい(伝聞)のでガス消費量を抑える意味でもこれでいいと思います)
[profile.release]というのはrustの実行時のセッティングくらいに思っておきましょう。
panic
というのはパニックと呼ばれるエラー検知時の動作です。デフォルトではスタックの巻き戻しですが、今回は"abort"を指定することで異常終了することができます。
lto
はリンク時最適化と呼ばれる他ファイルでの呼び出し時にメモリを最適化するオプションを有効化しています。
opt-level
は最適化のレベルのことで"z"を指定してあげると依存しているクレート(ライブラリ)は最適化されるものの、トップクレートは最適化されずデバッグ情報を扱えるようになるそうです(詳しくない)
シークレットコントラクトを書く その1
シークレットコントラクトの構造
まずはシークレットコントラクトがどんな構造をしているか見てみましょう。
#![no_std]
#![allow(unused_attributes)]
extern crate //使いたいクレート
use //使いたいライブラリ
//structとかのグローバル変数
#[pub_interface]
pub trait ContractInterface {
}
fn private_function() -> () {
}
impl ContractInterface for Contract {
#[no_mangle]
fn {
//さっき宣言したInterfaceの関数
}
という流れです。つまり
- 使うクレートとライブラリの指定
- グローバル変数の記述
- Contractで外部から呼び出せる関数のインターフェースの記述
- 外部から呼び出さない関数の記述
- 3で記述した関数をContractという構造体に実装する
という流れです。
使うライブラリを指定する
#![no_std]
#![allow(unused_attributes)]
extern crate eng_wasm;
extern crate eng_wasm_derive;
extern crate rustc_hex;
extern crate serde;
extern crate serde_derive;
extern crate std;
use eng_wasm::*;
use eng_wasm_derive::{pub_interface, eth_contract};
use rustc_hex::ToHex;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
まず、
#![no_std]
extern crate std;
use std::collections::HashSet;
でデフォルトで搭載されているstdライブラリをスコープから外します。これによって少しガスを節約できます。
そのあとに使うものだけスコープに入れます。今回はHashSetを使います。(HashSetは重複しない要素の集合体)
extern crate eng_wasm;
extern crate eng_wasm_derive;
use eng_wasm::*;
use eng_wasm_derive::{pub_interface, eth_contract};
この二つのクレートはenigmaのシークレットコントラクト開発用のクレートです。
eng_wasmは基本的に全部をクレートに入れ、eng_wasm_deriveは、pub_interfaceと使う機能をそれぞれスコープに入れます。今回はEnigma側からEthereumのコントラクトを呼び出すのでeth_contractを使用します。
extern crate rustc_hex;
extern crate serde;
extern crate serde_derive;
use rustc_hex::ToHex;
use serde::{Deserialize, Serialize};
この二つは基本的に使います。
特にシリアライズ、デシリアライズはstructをjsonなどに変換できるようにすることでEnigma側から呼び出せるようにするためには不可欠です。
グローバル変数を記述する
// 参加者の最大値です。これ以上の参加者は認められません。
static MAX_PARTICIPANTS: u16 = 1000;
// ステートに書き込む際(次回参照)のステートの名称です
static OWNERSHIP: &str = "OWNERSHIP";
static WHITELIST: &str = "WHITELIST";
static LOTTERIES: &str = "LOTTERIES";
static LOTTERY: &str = "LOTTERY_"; // 動的に変更します。IDが1のくじだったらLOTTERY_1と記述されます。
// 各くじのオーナー(主催者)の情報です。
#[derive(Serialize, Deserialize)]
pub struct Ownership {
owner_addr: H160,
deposit_addr: H160,
}
// 参加可能な参加者のリストです
#[derive(Serialize, Deserialize)]
pub struct Whitelist(HashSet<H160>);
// IDがどこまで使われたか、インクリメントすることで教えてくれます。
#[derive(Serialize, Deserialize)]
pub struct Lotteries(U256);
// くじの現在の状態です。(参加可能なのか、くじ引き可能なのか、終了後なのか)
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy)]
enum LotteryStatus {
JOINING = 0,
READY = 1,
COMPLETE = 2,
}
// くじの情報です。見方は1.くじのID 2.対象NFTのアドレス名 3.対象NFTのid
// 4.参加者リスト 5.参加者の最大値 6.当たった人 7.くじの状態
#[derive(Serialize, Deserialize)]
pub struct Lottery {
id: U256,
contract_addr: H160,
token_id: U256,
participants: Vec<H160>,
max_participants: U256,
winner: H160,
status: LotteryStatus,
}
// くじ情報を返す際の型
type LotteryInfo = (U256, H160, U256, U256, U256, H160, U256);
#[eth_contract("Deposit.json")]
struct EthContract;
まず、structには必ず#[derive(Serialize, Deserialize)]
がついてます。
#[eth_contract("Deposit.json")]
struct EthContract;
この部分はEthereumのスマートコントラクトの情報を取り込んでスマートコントラクトの構造体を作っています。
#[eth_contract("Deposit.json")]
のDeposit.jsonはスマートコントラクトのインターフェースを記述したjsonファイルです。
コントラクトのインターフェースを記述する
pub trait ContractInterface {
fn construct(owner_addr: H160, deposit_addr: H160) -> (); //シークレットコントラクトデプロイ時に呼び出す
fn add_to_whitelist(address: Vec<H160>) -> ();//ホワイトリスト追加
fn get_whitelist_size() -> U256;//ホワイトリストのサイズ呼び出し
fn create_lottery( //くじ作成
contract_addr: H160,
token_id: U256,
max_participants: U256,
owner_addr: H160,
) -> U256;
fn get_lotteries_size() -> U256; //くじが何種類今あるか返す
fn join_lottery(lottery_num: U256, address: H160) -> (); //くじに参加
fn get_lottery_info(lottery_num: U256) -> LotteryInfo; //くじ情報をえる
fn roll(lottery_num: U256) -> H160; //くじ実行して勝者を返す
}
インターフェースの記述は fn 関数名(引数) -> 返り値の型(返り値がないなら()を記述)
です。
想像以上に長くなってしまったのでここまでです。
宣伝
弊社turingumではEnigma開発者コミュニティEnigma Developer Guildの情報や開発に関する情報などをお届けするニュースレターを不定期配信しています。
ぜひこちらまでご登録ください。
なまはげでしたー
次回は具体的な関数の実装をやります。