gypsyR

【ICP #3】DEX作る 

2023-2-20

UNCHAIN の課題備忘録

ICP の DEX

  • ICP の安価なストレージを活かしデータの保存を全てオンチェーンで行う
  • 従来のオーダーブック形式をとる DEX は、売買の注文をオフチェーンで行い取引の実行をオンチェーンで行う

2. 準備

2-1.機能

  • II を使用したユーザ認証
  • オリジナルトークンを faucet から受取
  • DEX に対してトークンを入金・出金
  • オーダーを作成し、取引を実行

2-2.環境チェック

  • dfx --version
  • node --version

2-3.プロジェクト作成

  • dfx new icp_basic_dex
    フォルダ構成確認
  • tree -L 1 -F -a icp_basic_dex
    テストのため sample プロジェクト実行
  • cd icp_basic_dex
    プロジェクトをローカル環境へデプロイする前に、ローカルのキャニスター実行環境を起動する必要。キャニスター実行環境を立ち上げる。(ローカルのキャッシュ削除かつバックグラウンドで実行する)
  • dfx start --clean --background
    モジュールインストール
  • npm install
    実行環境へキャニスター登録・ビルド・デプロイ
  • dfx deploy
    サンプルプロジェクトでは 2 つのキャニスター(icp_basic_dex_backendicp_basic_dex_frontend)がデプロイ
    開発サーバーを起動
  • npm start

2-4.準備完了、キャニスター実行環境を停止

Ctrl+C でキャンセル。サーバーが停止しターミナル入力できるようになったら、dfx stop


3.DEX 基本機能実装

  • オリジナルの fungible token を発行
  • 規格にDIP20
  • ERC-20 トークン標準を ICP でも利用できる
  • オリジナルのトークンキャニスターをデプロイ

3-1. 設定ファイルにキャニスター書込

DIP20 のサブモジュールを取り込む

  • git submodule add https://github.com/Psychedelic/DIP20.git
  • 設定ファイルdfx.jsonにキャニスター追加
  • DFX を通じて開発者が実際にキャニスターとやりとりをする際に活用

トークンキャニスターのデプロイ

  • dfx start --clean --background
    次にデプロイするユーザプリンシパル(識別子)を変数に登録。

  • export ROOT_PRINCIPAL=$(dfx identity get-principal)

  • dfx deploy GoldDIP20 --argument='("Token Gold Logo", "Token Gold", "TGLD", 8, 10_000_000_000_000_000, principal '\"$ROOT_PRINCIPAL\"', 0)'
    キャニスターとやり取り

  • dfx canister call GoldDIP20 getMetadata
    他のトークンキャニスターも同様に。

4. キャニスター作成

DEX 上で扱うオリジナルのトークンをユーザー自身が取得できるよう、faucet 機能を付ける。Faucet の役割をするキャニスターを作成する。作成後、DIP20 キャニスターと Faucet キャニスターをデプロイして、DIP20 キャニスターの mint メソッドをコール ⇒ 一定量のトークンを保持させる。

motoko

  • モジュール間で共通して使用したいユーザ定義の型や別のキャニスターのメソッドを呼びだすためのインターフェースを特定のファイルにまとめて定義し、インポートして使用する方法が一般的。
  • types.moに DIP20 キャニスターから呼び出したいメソッドや faucet キャニスターが使用するユーザ定義の型を記述(ROS でいう msg に近い形式か)
  • main.moに faucet キャニスターのコードを記述する
  • hashMap は 3 つの引数を取る。第一引数に初期のサイズ、第二引数に HashMap の key 同士を比較するために使用する関数、第三引数に key をハッシュ化するために使用する関数を指定。Principal 型には equal と hash がそれぞれ定義されていて、ここではその関数を指定。

Faucet キャニスターの機能

  • ユーザにトークンを転送

  • トークンを渡したユーザのデータを保持(トークンの配布を一人のユーザに対し一度だけと制限するため)

  • トークンきゃにすたーの principal 受取、戻り値は faucet の型

  • 配布可能かチェック

    • ユーザが残高が配布する量より少ない場合エラー
    • ユーザがトークンを受け取ってないかチェック
  • トークン付与の記録

    • ユーザ principal とトークンを保存

テスト

  • test.shにシミュレーション書ける
  • default:DIP20 と Faucet キャニスターデプロイ
  • user1, user2 がトークン取得

5. 入出金キャニスター

  • dex 内にユーザが預けているトークンを取得して返す get 関数(トークン pricipal とトークン量のマップを返す、なかったら null)
  • addToken 関数は入金を記録。balance_book のデータを更新。
  • removeToken は出金を記録し balance_book 更新。戻り値?Natで、最初に定義した get 関数と同様 Option 型を返す
  • 残高が 0、不足してたらエラー、問題なければ残高を返す
  • hashEnoughBalance は dex 内に入金されてるユーザのトークン量が十分か確認するときにコールされる関数
  • types.mo に DIP20 キャニスターからコールしたい関数・扱う型と、DEX が使用するユーザー定義の型を追加

5-2. main.mo, types.mo 修正し、入金、出金の機能実装

deposit 関数

  • deposit カンスはユーザが DEX にトークン入金する際にコールされる
  • トークンの転送処理が重要
  • DEX 内(icp_basic_dex_backend キャニスター)にユーザのトークンを転送。ただし transfer メソッドではできない、トークン所有者はユーザのため、transferFrom を使用する。
  • 転送前にユーザのトークン残高 allownce メソッドで取得。fee に満たない場合エラーを返す。
  • 問題なければ transferFrom メソッドでユーザから DEX 内にトークンを転送。転送されるのは手数料を引いた分になる
  • 転送官僚したら、balance_book モジュールの addToken を呼び、dex 内のトークンデータを更新

withdraw 関数

  • ユーザが DEX からトークンを出金する際にコールされる
  • DEX ないにユーザが保有するトークン量と引き出したい量を比較。不足ならエラー
  • 問題なければ transfer で転送
  • 転送完了したら、balance_book の更新

fetch_dif_fee は内部関数

  • 転送を行うトークンの手終了取得
  • getMetadata メソッドをコールし、データの取得可能.データ型は types.mo に定義

getBalance 関数

  • 引数で渡されたユーザが dex 内で保有するトークンの量を返す

テスト

  • rm -rf .dfx
  • dfx start --clean
  • bash ./scripts/test.sh

6. オーダー作成機能

BalanceBookモジュールをインポート。取引が成立するものがあれば取引を実行する、取引を実行する際に DEX 内のトークンデータを書き換える必要がある。そのために balancebook モジュールの関数をコールしたいのでインポート

6-1. var orderes

  • オーダーはマップ構造で保存。各オーダーに割り振られる ID でオーダーの検索を簡素化する

6-2. getOrders, getOrder

保存されているオーダーを取得する関数。main.mo に定義した getBalance 関数同様に getOrder 関数は引数に渡されたオーダーの ID に応じてデータを返す

6-3. cancelOrder, addOrder

  • addOrder 内で detectMatch 関数をコール(成立する関数があるか確認する関数)
  • for 文で全てのオーダーを確認し取引が成立する if 文の条件に一致した場合 processTrade をコール。

6-4. ProcessTrade

  • 実際にトークンの保有データを更新
    userX
    [ TGLD -100, TSLV + 100]

  • 取引の内容で order_x の作成者のトークン残高を更新
    userY
    [ TSLV -100, TGLD + 100]

  • 取引の内容で order_y のトークン残高を更新

  • 取引が完了したオーダーを削除して終了

ここまででオーダーを作成して取引を行う部分が完了。

6-5. main.mo の編集 取引機能を完成

Exchange モジュールをmain.moからコールしてユーザが取引を実行できるようにする。そのためにはtypes.moを編集してオーダーに関するデータ型を追加する必要がある。

  • withdraw 関数について出金を行う際に、ユーザーが出しているオーダーに対して DEX 内のトークンが不足した場合、オーダーを削除する

← ホームへ戻る