2022年3月2日に開催したエンジニア向け勉強会NFTプラットフォームの作り方 ー FanTopの裏側。多くの方にご参加いただいたのですが、残念ながら都合が合わなかったり、後でイベントを知ったという方もいらっしゃるのではないでしょうか。
そうした方々向けに、連載第3弾としてテキスト起こし版を作成しました。スライド資料と合わせてご覧ください。
FanTopの裏側 スマートコントラクト編
NFTマーケットプレイス「FanTop」で扱われるNFTは、Flowというブロックチェーン上で取引され、Cadence言語で書かれたFlow向けのスマートコントラクトによって構築されています。 国内でも事例の少ないFlow Cadenceによるスマートコントラクトをどのように設計し、開発したのか。その中身を解説していきます。
登壇者紹介
菊地 諒
電子書籍元年と呼ばれる2011年頃以前から初期のスマホアプリやライブラリの開発に携わる。2017年にメディアドゥに中途入社。 React NativeをきっかけにWeb技術に転向し、Web3の世界へ。
自己紹介
菊地諒と申します。年齢は38歳、愛媛県松山市の出身です。趣味は自宅のIoT化です。例えばこの部屋の明かりは人感センサーで人がいると点いて、いなくなると15分ぐらいで勝手に消えるなど、家のほとんどの電気を自動化しています。Wi-Fiに繋がるデバイスが既に40台以上でWi-Fiが限界なので、どうにかしないといけない状態です。
分かる技術としてはObjective-C/Java(Android)/Ruby/TypeScript/Vanilla JSです。Vanilla JSはフレームワークに依存しない、ブラウザ標準のJavaScript APIを利用する考え方のJavaScriptです。後はReactに長く携わっていたのと、最近はRustでWebAssemblyを作ったり、今回のFlow Cadenceを1年ぐらい行っています。分からない技術としてはEthereum Solidity、Flow以外のブロックチェーンです。まったく知らないという訳ではなく、開発では使用していませんといった程度です。
今回はFlow Cadenceの話をしますが、Ethereumが分かっていないと何も書けないものではなく、分かりやすい言語ですよという話をしたいと思っています。
スマートコントラクトとCadence
テーマがスマートコントラクトなので、まずスマートコントラクトとは何かについてお話しします。コントラクトは契約のことで、スマートコントラクトはブロックチェーン上で実行されるコードになります。コードの内容は誰でも参照できます。
コントラクトを実行した履歴は、全てブロックチェーン上に電子署名付で記録されるので、改ざんできません。改ざんができない = 契約として機能するので、コントラクトと言います。
コントラクトにはコントラクトフィールドといって内部状態を保存する仕組みがあり、そのデータもブロックチェーン上に記録されています。そして、FlowではコントラクトをCadenceという新しい言語で記述します。これはEthereumで言うところのSolidityという言語と同じようなものです。
CadenceはDapper Labs社によって開発されたFlowのためのスマートコントラクト言語です。特徴としては強力な静的型付けが挙げられます。SwiftやKotlin、TypeScriptなど最近の構文を取り入れているので、かなり可読性が高く書きやすい言語です。
また、Cadenceはリソース指向なプログラミング言語です。Rustにも変数に所有権という概念があって、使い終わったメモリ空間を開放する仕組みがあります。Cadenceではこれをリソースという概念に落とし込んで、その仕組みを応用して代替不可能性を担保できます。
加えて、関数とトランザクションの前後に条件を記述して、テストができます。pre-conditionsとpost-conditionsという表現ですが、これはC言語におけるAssertに近いものです。ある状態でこの関数を実行した後、状態がこのようになっているはず、ということを記述できます。条件を満たしていなければ、トランザクションが失敗しますので、変なデータや状態にならないようにできます。
このCadenceは、Flowの公式サイトでスマートコントラクトを実際に書いて試せるプレイグラウンドがあります。実際に、ブラウザ上で自分でコントラクトを書いて動かせるのがとても便利です。ぜひ皆さんも試してみてください。
FanTopにおけるスマートコントラクト
ここからFanTopの話に入ります。FanTopはAWS上に構築された従来型のシステムと、Flowブロックチェーン上に構築されたスマートコントラクトの連携によって動作するWebサービスになります。チームの間ではAWS側をoff chain、Flow側をon chainと呼んで区別しています。off chainにはフロントエンドとバックエンド、そしてWorkerという役割があります。on chainにはコントラクトという役割があります。それがスマートコントラクトであり、今日解説する部分になります。 この図ではExecutionやCollection、Access、Verification、Consensusなどそれぞれのノードによってブロックチェーンが構築されていますが、その上にコントラクトが乗っかって動いています。
このスマートコントラクトを私が担当して開発していました。元々プロトタイプがあり、それをもとに再設計を行っています。本実装をした後、Flowチームにレビューとデプロイをしてもらう形で、最終的にメインネットに乗せています。
最初に私がプロジェクトに参画した時点で、社内用デモとしてプロトタイプが存在していました。このプロトタイプはメディアドゥのトップエンジニアチームによって、NFTを扱うマーケットWebアプリという形で開発されています。社内で検証を行った結果大成功して、FanTopの本開発がスタートしたと聞いています。この頃から私をはじめ複数のメンバーがジョインして、みんなで本開発を開始する流れになりました。
ただ、この時点ではほとんど誰もFlowについてよく分からない状態だったので、社内勉強会を開いてみんなでFlowに入門しました。その後、私がコントラクトをメインで担当することになったので、コントラクトについて読み込んで、構築していきました。
既存のon chain部分はFirebase上で実装されており、プロトタイプのプロジェクトから独立していました。短期間で動くものを作るためにそういった構成になっていましたが、プロダクト開発の段階では冗長なので、統合していきました。
そしてプロトタイプの中で動いていたスマートコントラクトは、Dapper Labs社が作ったKittyItemsというFlowを使ったマーケットのサンプルプロジェクトを使用していました。KittyItemsには、様々な機能があらかじめ実装されていましたが、今回のプロダクト要件に合わない部分も多々ありました。そこで、一旦破棄して最初から作り直すことにしました。
作り直す際には、同じくFlowを使ったマーケットで有名なNBA Top Shotの実装をベースにしています。実はNBA Top ShotのスマートコントラクトはGitHub上で提供されていて誰でも見ることができます。dapperlabs/nba-smart-contractsというリポジトリがあるので、興味のある方はぜひご覧ください。
プロトタイプになかった要件の実装
コントラクトの再設計をしたのですが、プロトタイプにはなかった要件が複数出てきました。
Flowを使ったサービスでは、Ethereumのようなブロックチェーンのサービスと同様に、ユーザー側にウォレットが必要です。そこでBloctoというウォレットを使っています。BloctoにはMetaMaskのような拡張をインストールしなくても、必要なときにウォレットとしてブラウザの中で動作させる仕組みが提供されています。
ただし、認証はブラウザとBloctoサーバー間で行われるので、その認証状態をoff chain側のFanTopサーバーへ渡す方法を考える必要がありました。他にも法定通貨(クレジットカード)を使うという課題がありました。FanTopではNFTを購入する時にクレジットカードで決済しますが、外部の決済サービスと連携する必要があります。
ここに、運用時のヒューマンエラーを考慮するという課題があります。人が操作するシステムは、どうしてもミスが起きます。間違った情報を入力してしまった時、どのように訂正できるかを考えなければいけません。つまり、安心してコンテンツを預けられるようにするということです。FanTopでは他社さんからコンテンツをお預かりして販売します。セキュリティ上の様々なリスクに対して、可能な限り対策をしなければなりません。それらを踏まえて設計し、次のように実装しました。
認証状態をバックエンドと共有するために、ユーザーがWebページ上でログインボタンを押したときに、トランザクションを実行します。そして認証コードを発行して、イベントを発火し、それをWorkerが受け取る構造になっています。これにより、どのユーザーが今アクセスしているユーザーなのか紐付けできる仕組みを作りました。しかし、Flow自体にユーザーメッセージ認証という仕組みができて、任意のデータに署名をつけられる構造ができました。そこで、この仕組みを使って、簡単に実装できています。
法定通貨を扱う部分は、NFTを購入するときにブロックチェーンと関係ない外部の決済サービスを使っています。その結果をもって、NFTを発行します。そのため、先にoff chainでトークンが発行される予定の参照用レコードを作り、その参照用レコードが持つIDをパラメータとしてスマートコントラクト上でNFTを発行する流れにしています。
運用上のヒューマンエラーを考慮して、NFTのアイテムメタデータにバージョンの情報を持たせています。また、後からメタデータを更新できるようにしています。もし間違った状態で発行されたとしても、それを残せます。これを、全てのバージョン情報を持たせるという形で残せるようにしました。
安心してコンテンツを預けられるようにするために、特殊な仕組みをコントラクト上に作っています。万が一、管理権限や秘密鍵が盗まれてしまった状態でも、そのアドレスを監視して、想定外の挙動を発見した際には、その権限をあとから剥奪できます。
実際に書いたスマートコントラクトの構成と動きを図にすると以下のようになります。
0x01、0x02にユーザーのアドレスが書いてあります。0x03はFanTopのコントラクトを保有するアカウントです。
コントラクトにはコレクション(NFTをまとめて保管する箱)とアイテム(構造体の単位)、リソースが定義されています。ユーザーはFanTopを利用する際に、保管庫としてコレクションを作って、自分自身のストレージに保存できます。
この時、データの実体はFlowブロックチェーン上にあるノードが持っています。このデータを動かすには、ユーザーのウォレットにある秘密鍵が必要なので、盗難される心配はありません。FanTopのNFTを発行する権限(Mint)を持つMinterは、ユーザーからの購入操作に応じてNFTを発行し、コレクションに渡す(入れ込む)ことができます。さらにユーザーは自身が持っているNFTを取り出して、他の人のコレクションに渡すこともできます。
NFTはCadence言語の特徴であるリソースという形で定義されています。これは参照はできますが、コピーはできません。移動する時は、トランザクションを発行しますが、これは自分自身が持っているNFTでしか動かせないようになっています。実際に書いたものは、mediadotech/smart-contractsにて公開しています。気になる方はぜひご覧ください。
こうして書いたスマートコントラクトは、最終的にセキュリティ上のリスクがないかDapper Labs社のFlowチームが直々にチェックしてくれます。レビューの過程では、それぞれの定義の要件やどのように設計したのかを文面で確認されました。
レビューする前に、ブロックチェーンにおけるテスト環境のような場所(Testnet)にデプロイして実際に動かすように伝えられました。Dapper Labs社から見える場所ということだったので、GitHubで公開しています。
セキュリティ上の懸念などがあればフィードバックが返ってきますので、都度修正して再申請します。アプリの申請に似た流れです。最終的にOKであれば、Flowチームがメインネットにデプロイします。
リリースまでに起きたこと
ここからはリリースまでに起きたことをお話しします。1つ目はストレージの保存場所問題です。Flowではアカウントごとにストレージという概念があり、そこにユーザーが持つNFTのデータを保存します。このストレージにはパスの概念があり、 /Storage/FanTop/TokenCollection
のようなパスを指定します。このパスがステージングとテスト環境で、被ってしまうとエラーになることが分かりました。
Cadenceの言語仕様として、パスはリテラルで記述する以外に指定方法がなく、動的に変えられません。そのため、FanTop/Collection/〜
といったパスをステージングやテストで動的に変更できず、どうしたものかと悩みました。結局、それぞれの環境ごとに違うパスになるように、トランザクションそのものを書き換えて、文字列置換してから実行するといったトリッキーな方法で解決しました。
続いて2つ目、辞書型Enumの実行時エラー問題です。辞書型というのは、Dictionary型のことです。配列型や連想配列型といったりする場合もあります。このDictionaryのキーには好きな型を指定できるのがCadenceの仕様です。数値型やEnum型も指定できるので、Enum型をDictionaryのキーにしていました。当初は問題なく動いてたのですが、ある時Cadence言語そのものがアップデートされて、利用できなくなりました。
その結果、Flowのエミュレータやテストネット、トランザクションがすべて失敗しました。仕方がないので、公式リポジトリにレポートした上で、EnumからStringを使う変更を加えて解決しました。一度作ったものを後から書き換えることになったので、ここはかなり苦労しました。
3つ目は配列型や辞書型がMutable、つまり変更可能ということです。不変にできないのが難点でした。Swiftに似た言語なのですが、Swiftのようにletで後から変更できないようにはできませんでした。
Cadenceではletでも配列型や辞書型の内容は書き換え可能です。JavaのListに近いものです。これはレビューを出した時に、変更できるようになっているので公開すべきでないと指摘されました。そのため、ArrayやDictionaryのようなものについては、Javaのようにフィールドは非公開でgetterを用意するといった対応をします。
4つ目はキー順序不一致問題です。辞書型のキー順序がアップデートでバラバラになったという問題です。多くのプログラミング言語で辞書型のキーは順序が保持されます。Cadenceでも、エミュレータやテストネットでは挿入した順序が維持されています。A=1、B=2といったことをすると、キーの順序はA、Bという順番で入っています。しかしこれが、ある時のアップデートで突然順序が維持されなくなりました。
CadenceはGo言語で作られています。そして、Go言語の実装にA Treeという新しい配列や連想配列を扱うようなライブラリがあります。このA Treeが更新されてしまったことで、挙動が変わってしまいました。
改めてCadenceのドキュメントを読むと、維持されません(unordered)と書かれています。ユニットテストで、このデータがこの順番で出てくるはずというテストを書いていたのですが、すべてエラーになってしまいました。これは、テストのMatcher部分をカスタマイズして対応しました。
FanTopは2021年10月にサービスインしました。その際、本番でリリースすると何かしら問題が起きるかもと思っていました。しかし、事前にかなり細かくテストをしていたこともあって、スマートコントラクトの部分でのトラブルもなく問題なく動作しています。
質疑応答
Flowと他のブロックチェーンの一番の違いは、何でしょうか?
私の理解では、Flowはトランザクション手数料がとても安いです。開発時点では、そもそもトランザクション手数料はまったく取られませんでしたし、今でも0.02円ととても安いです。Ethereumだと4,000円くらいかかると聞きますので、それと比べると1つのNFTを発行するためにトランザクションを実行するのが安いのは何よりのメリットだと思います。
あと、EthereumのSolidityと比べると、言語仕様がモダンで書きやすいし、安全に書けると思っています。この辺りは実際にスマートコントラクトを書いて試していただきたいです。
FanTop上からNFTに紐付くコントラクトが見られますか?
直接は見られませんが、トランザクションの履歴を見て、そこからリンクをたどっていけば見られます。Flowscanでスマートコントラクトのアドレスを見ると実際の内容が見られるのですが、FanTop上にFlowマークでそのリンクを貼っています。Flowマークをクリックすると保有権が切り替わった時のトランザクションをFlowScan上で見られます。
NFT出品者が独自に作成したコントラクトを利用できますか?
今のところFanTopがオーナーのコントラクトをみんなが使っている形になります。将来的には分かりませんが、現在はそのような構造です。これはビジネス要件とも関わってきますが、OpenSeaのようにオープンな、誰でもコピー品でも何でも出品できるマーケットは想定していません。あくまで私たちが確認したトークンのみが出品できる仕組みです。絶対対応できないわけではなく、権限設定などの仕組みも入れてあるので、将来的に対応していくことは可能な設計です。
独自コントラクトについてですが、Flowはまだ開発中ということもあって、Flowのメインチームがスマートコントラクトをチェックしています。そのため、誰でも簡単にコントラクトを上げられる状態ではありません。彼らのマイルストーンとして、今年中に誰でも自由にスマートコントラクトを上げられるようにする計画で開発が進められています。そのタイミングから、徐々に独自コントラクトが出てきたりして、自由に色々なオリジナル機能をNFTに対して実装する取り組みが増えるのではないでしょうか。
販売中のアセットのハッシュ値を何らかの方法で入手した第三者が、そのハッシュ値に対して所有権をFlow上に勝手に記録するのを技術的にどう防止していますか?
まずブロックチェーン全般の技術についてですが、ブロックチェーンはコピーを防ぐ技術ではありません。ただ、コピーしていたら検証することで、コピーしていることが分かる仕組みがあります。大元のアドレスさえ分かれば、確実にこの人から発行されたものかどうかを検証できる技術になります。そのため、コピー自体を防ぐのにエネルギーを割くのではなく、後日、本物かどうかを検証できる技術として使われています。
その観点でハッシュ値を入れることがなぜ大事かと言うと、最初に発行してきたものから全く改ざんが行われていないことを、ブロックチェーン外に記録されたデータに関しても証明するのに利用しているからです。
そこで、第三者が発行したNFTを区別する方法があるかというと、Flowアドレスが異なるからこちらが本物である、といった方法は一般的にはまだ難しい状態です。そのため、今は発行するNFT自体に制約をかけています。ただ、ブロックチェーン自体は完全にon chainで管理しているので、将来的にそういったオープンな世界へも行けるようシステムを設計しています。
NFTを売りたい場合は、まずNFTをFanTopに預ける必要があるのでしょうか?またはFanTopでNFTの売却登録をして、それとは別に誰かにNFTを送れるでしょうか?
はい、できます。NFTをFanTopに預けるわけではないです。実はFanTopにNFTの保有権を切り替える機能を委譲してもらっています。なぜかというと、私たちはクレジットカードの決済機能を入れていて、決済はoff chainで発生する出来事だからです。それをFanTopがトラックしてから、ブロックチェーン上の保有権を書き換えています。
その時、たとえばクレジットカードの決済が通った瞬間にNFTを誰かに譲渡されてしまうと、FanTopはNFTを書き換えることができなくなります。それを防ぐために、クレジットカードの決済が通ったらすぐにNFTの保有権を書き換えられる仕組みを入れています。そのためにNFTを預けてはいませんが、保有権を変えられる権利をFanTopに委譲してもらっています。もちろん保有権はユーザーにあるので、自由にトランザクションを書けば、譲渡は自由にできる状態です。
- ブロックチェーンの選び方 ― 日本初Flowを利用したサービス開発の裏側
- NFTマーケットプレイスのアーキテクチャ設計
- FlowブロックチェーンとCadence言語の実用 ― FanTopの裏側
- ブロックチェーン上のデータを管理する仕組み