ICP Rust CDK学習メモ
  • ICP Rust CDK学習メモ
  • ⚙️開発環境準備
  • 開発
    • テンプレート生成 (dfx new)
    • Frontend開発
      • 1. シンプルなHTML
      • 2. Canister呼び出し
    • Backend開発
      • 1. Hello
      • 2. データ更新/参照
      • 3. ic_cdk::caller()
      • 4. ic_cdk::call()
      • 5. 動的画像生成
      • 6. staticファイル参照
      • 7. HTTPS outcalls
      • 8. Threshold ECDSA
    • Bootcamp
      • Calculator ➕✖️➖➗
      • Homework diary 📔
      • Student wall 🎨
      • MotoCoin 🪙
      • The Verifier 👨‍🏫
      • The Dapp 🚀
  • サンプル
    • 📔Diary
  • 技術情報
    • 🪪Principal
    • 🪪Internet Identity
    • 🪙Token
    • 🖼️NFT
    • 🗝️VetKey
  • ツール
    • dfx
      • dfx identity
    • quill
  • 参考情報
    • 🔗リンク集
GitBook提供
このページ内
  • 1. Rustプロジェクト作成
  • 2. Cargo.tomlの編集
  • (1) IC関連ライブラリ追加
  • (2) crate-type設定
  • 3. dfx.jsonの作成
  • 4. candidの作成
  • 5. lib.rsの編集
  • ソース説明
  • 6. Unitテスト
  • Test your canister code even in presence of system API calls
  • 7. Local Canisterの起動
  • 8. Local Canisterへの配備

役に立ちましたか?

GitHubで編集
  1. 開発
  2. Bootcamp

Student wall 🎨

前へHomework diary 📔次へMotoCoin 🪙

最終更新 1 年前

役に立ちましたか?

2023年5月に開催されたのプロジェクトをRust言語で実装します。

1. Rustプロジェクト作成

Rustのプロジェクト「day3」を作成します。cargo newコマンドを--libオプションを付与して実行します。

$ cargo new day3 --lib
$ cd day3

生成されたファイルは以下の通りです。

day3
├── Cargo.toml
└── src
    └── lib.rs

2. Cargo.tomlの編集

(1) IC関連ライブラリ追加

ライブラリを使用します。最新バージョンでよいかと思いますので、以下のように実行ます。

$ cargo add candid ic-cdk serde

(2) crate-type設定

Canister上から関数が正しく呼び出させるようcrate-typeをcdylibにします。

[lib]
crate-type = ["cdylib"]

3. dfx.jsonの作成

Canisterの定義を行います。

{
  "canisters": {
    "day3": {
      "candid": "./day3.did",
      "package": "day3",
      "type": "rust"
    }
  },
  "defaults": {
    "build": {
      "args": "",
      "packtool": ""
    }
  },
  "version": 1
}

4. candidの作成

dfx.jsonの [canisters] > [day3] > [candid]項目に指定したファイルに、Canisterに配置するDappが提供する関数のI/Fを定義します。

※MotokoのResult (ok, err)とRust標準のResult (Ok, Err)で大文字小文字に違いがあります。MotokoとI/Fを合わせたい場合には別途Result型を定義してください。

type Content = variant {
  Text: text;
  Image: blob;
  Video: blob;
};

type Message = record {
  content: Content;
  creator: principal;
  vote: int;
};

type Result = variant {
  Ok;
  Err: text;
};

type ResultMessage = variant {
  Ok: Message;
  Err: text;
};

service : {
  writeMessage: (Content) -> (nat);
  getMessage: (nat) -> (ResultMessage) query;
  updateMessage: (nat, Content) -> (Result);
  deleteMessage: (nat) -> (Result);
  upVote: (nat) -> (Result);
  downVote: (nat) -> (Result);
  getAllMessages: () -> (vec Message) query;
  getAllMessagesRanked: () -> (vec Message) query;
};

5. lib.rsの編集

cargo newコマンドで生成されたlib.rsの中身をクリアして、day3用のプログラムを作成します。

  • writeMessage()

  • getMessage()

  • updateMessage()

  • deleteMessage()

  • upVote()

  • downVote()

  • getAllMessages()

  • getAllMessagesRanked()

Rust言語仕様の理解が十分でないため、作成したソースコードは所有権まわりをはじめ最適化されていない可能性がありますのでご注意ください。もしも、おかしな実装等が見つかりましたらが、ご指摘いただけますとさいわいです。

ソース説明

(a) 関数名

Canisterが提供する関数の名前がcamelCase形式なのに対し、Rustは一般的にSnake_case形式を推奨しているため、コンパイル時に以下のような警告が出ます。

warning: variable `xxx` should have a snake case name

先頭行に以下を入れておくことで、警告を抑止することができます。

#![allow(non_snake_case)]

(b) Content列挙型

扱うコンテンツを列挙型 (enum)として定義しています。

Textm Image, Videoのいずれかの値をとり、それぞれ異なるデータ型のデータを持ちます。

enum Content {
    Text(String),
    Image(Vec<u8>),
    Video(Vec<u8>),
}

(c) Message構造体

Contentとvote、creatorから構成される構造体を定義します。

Principal型はICのPrincipal IDを示しており投稿者も記録します。

struct Message {
    content: Content,
    vote: i128,
    creator: Principal
}

(d) Canisterの保持データ

Canister内に保持するデータは以下の2種類です。

  • メッセージ自動採番用

  • メッセージIDをキー、Messageデータを値とするBTreeMap

以下のようにスレッドローカルデータで保持するのが作法のようです。

thread_local! {
    static MESSAGE_ID: RefCell<u128> = RefCell::new(0);
    static WALL: RefCell<BTreeMap<u128, Message>> = RefCell::new(BTreeMap::new());
}

※MotokoではHashMapが使われておりますが、ここではgetAllMessages()で順序性を維持できるようBTreeMapを使用しています。

6. Unitテスト

TODO: IC色のあるUnitテスト方法について後日整理する

#[cfg(test)]
mod tests {

  use super::*;

  #[test]
  fn writeMessage() {
    let id = crate::writeMessage(Content::Text(String::from("TEST")));
    ︙
  }
}

ソース中にIC色があるとUnitテストが上手く行えないようですので、以下のいずれかの方法でテストするとよいでしょう。

  • Canisterに配置してテストを行う

  • IC CDKのAPIを直接呼ばずに抽象化して、テスト時はスタブを使うようにする

後者の方法として、以下の記事が参考になりそうです。

Test your canister code even in presence of system API calls

7. Local Canisterの起動

Local Canisterを起動します。

--backgroundオプションでサービス常駐でき、--cleanを付与すると真っ新な状態でLocal canisterを起動できます。

$ dfx start --background --clean

8. Local Canisterへの配備

$ dfx deploy

Cargo.lockがディレクトリに存在しない場合dfx deployがエラーとなりますので、cargo generate-lockfileを実行するとよいでしょう。

に相当するcandidを用意します。

と同じように、以下の関数を実装します。

やのようにUnitテストを記述してcargo testを実行したところ、ロジックにICのPrincipal型が含まれることが原因で、「xxxx should only be called inside canisters.」のようなエラーが出ました。

Motoko Bootcamp Day 3
ic-cdk
Cargo.toml
dfx.json
Motoko Bootcamp Day3 📺 Interface
day3.did
Motoko Bootcamp Day 3
lib.rs
Day 1
Day 2
https://internetcomputer.org/docs/current/developer-docs/security/rust-canister-development-security-best-practices#test-your-canister-code-even-in-presence-of-system-api-calls