2019年6月30日にMOLDEX αをリリースしましたが、ここで、MOLDEX αのシステムの概要について次の3つの軸に沿って簡単にまとめます。Dappsをはじめとした今後のブロックチェーン周りの開発の参考になれば幸いです。
- DEXのコントラクトについて
- サーバーサイドについて
- ブラウザwalletについて
MOLDEX αの構成は処理性能および非機能要件(可用性、運用・保守性など)、ユーザーからみたデザイン性、将来的なUXの向上などを意識した作りになっています。また、MOLDEX αではブロックチェーンとの相性を意識して、フルサーバレスで構成しています。
MOLDEX αの使い方については、前回の記事を参考にしていただければと思います。
DEXのコントラクトについて
MOLDEX αで使用したコントラクトは、github、またはEtherscanで確認することができます。MOLDEX αでは、ERC721とERC20の交換(ETHとの交換も可)ができるように、trade関数(後述)を定義しました。
アセットコントラクト
DEXコントラクト側でトークンを交換する仕組みを構築する際には、ERC20およびERC721で定められているapprove()とtransferFrom()という2つの関数を利用します。
- approve()
approve()関数は、第三者(この場合、DEXコントラクト) に送信者の残高からトークンを転送することを許可するために用いられます(ERC721の場合は、tokenIdを指定)。トークンはallowedというmapping型のデータ構造に保存されます。
// ERC20
function approve(
address _spender,
uint256 _value
)
public
returns (bool)
{
allowed[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}// ERC721
function approve(address _to, uint256 _tokenId) public {
address owner = ownerOf(_tokenId);
require(_to != owner);
require(msg.sender == owner || isApprovedForAll(owner, msg.sender)); tokenApprovals[_tokenId] = _to;
emit Approval(owner, _to, _tokenId);
}
- transferFrom()
transferFrom()関数は、第三者(この場合、DEXコントラクト)によりトークン転送を行うために用いられます。allowedの残高以下しか第三者(msg.sender)は引き出せないようになっています(ERC721では、指定されたtokenIdしか引き出せない)。
// ERC20
function transferFrom(
address _from,
address _to,
uint256 _value
)
public
returns (bool)
{
require(_value <= balances[_from]);
require(_value <= allowed[_from][msg.sender]);
require(_to != address(0));
balances[_from] = balances[_from].sub(_value);
balances[_to] = balances[_to].add(_value);
allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
emit Transfer(_from, _to, _value);
return true;
}
// ERC721
function transferFrom(
address _from,
address _to,
uint256 _tokenId
)
public
{
require(isApprovedOrOwner(msg.sender, _tokenId));
require(_from != address(0));
require(_to != address(0));
clearApproval(_from, _tokenId);
removeTokenFrom(_from, _tokenId);
addTokenTo(_to, _tokenId);
emit Transfer(_from, _to, _tokenId);
}
- allowance()
allowance()関数は、approve()関数でallowedに割り当てたトークンの値を返すview関数です。
ERC721の場合は、同じように、getApproved()関数が準備されており、tokenIdからそのトークンを移転する権利を持つ(すなわち、approveされた)アドレスを返すview関数になっています。
// ERC20
function allowance(
address _owner,
address _spender
)
public
view
returns (uint256)
{
return allowed[_owner][_spender];
}
// ERC721
function getApproved(uint256 _tokenId) public view returns (address) {
return tokenApprovals[_tokenId];
}
アセットの交換の流れ
TakerはToken Contractに対して approve()関数を実行し、任意の数量の moldcoinを転送することを承認します( websiteではdepositという表現をしているが実際にはapproveの処理)。
MakerはDex Contractのtrade関数を実行するのに必要なデータをサーバーに渡します。Takerは、Dex Contractのtrande関数を実行するのに必要なデータをサーバーに渡します。
サーバー側で、MakerとTakerのデータが集まったら、trade関数を実行します。trade関数内では、Dex Contractに対して許可されているtransferFrom()を実行します。この時transferFromで移動するトークンはapprove()関数で承認されている必要があります。
transferFrom()関数の実行し、TakerからMakerへのmoldcoinの移転、およびMakerからTakerへのERC721アセットの移転が完了します。
1. approve関数で交換準備
Taker側は、moldcoin(ERC20)でERC721のアセットを購入します。DEX Contractが、moldcoinを扱えるようにするために_spender
をDex Contractとして、任意の量のmoldcoinをapproveします。Maker側は、ERC721を売りに出します。DEX Contractが、ERC721アセットを送信できるようにするために_spender
をDex Contractとして、approveをします。
2. 署名つき注文データの送信
trade関数を実行するにあたり、必要な注文データをサーバーに送ります。
Makerは、次の6つの要素を順にhash化してorderHash
を生成し、秘密鍵で署名します。
- Dex Contract Address
- ERC721 Token Contract Address
- ERC721 Token ID
- Maker Address
- ERC20 Token Contract Address
- ERC20 Amount
Takerは、次の2つの要素を順にhash化してtradeHash
を生成し、秘密鍵で署名します。
- orderHash
- Taker Address
3. trade関数の実行
サーバー側では、主にrawdata
の検証と署名の検証を行います。rawdata
は、先ほどあげた注文データになります。Maker/Taker側からは、rawdata
+ orderHash
またはtradeHash
がサーバー側に送られます。サーバー側では、rawdataの中身が正しいかどうかハッシュ関数に通して確認します。また、秘密鍵で署名した時のデータである、v, r, s
の値と元データから公開鍵を一意に決定することができるので、署名が正しいかどうかの確認も行います。
Dex Contractのtrade関数の中でもecrecover(hash, v, r, s)
を利用した署名の検証は行われており、万が一、自分が出した注文以外のaddressが帰ってきた場合は、revertする仕組みになっています。
4. transferFrom関数の実行
Dex Contract がmsg.senderとして、ERC20トークンの移動およびERC721トークンの移動のためにtransferFrom関数が2回実行されます。
proxy contractの利用
Ethereumの最大の利点の1つは、資金移動に関わるすべてのトランザクション、deployされたすべてのコントラクト、および1つのコントラクトに対して行われたすべてのトランザクションが、ブロックチェーン上で公開され、かつ不変であるということです。これまでに行われた取引を隠したり修正したりする方法はなく、Ethereumネットワーク上のどのノードでもすべてのトランザクションの有効性と状態を検証できることで、Ethereumは非常に堅牢な分散システムになります。
しかし最大の欠点は、スマートコントラクトのソースコードがdeployされた後でそれを変更できないことです。集中型のアプリケーション(Facebook、Airbnbなど)で作業している開発者は、バグを修正したり新しい機能を導入するために頻繁にアップデートを行っています。この伝統的な開発パターンをEthereum上で実行することは不可能です。
すでにdeployされているスマートコントラクトのコードをアップグレードすることはできませんが、メインロジックがアップグレードされたかのように新しいデプロイされたコントラクトを使用できるようにするプロキシコントラクトアーキテクチャを設定することは可能です。
今回のDex Contractは、α 版ということもあり、今後なんども修正を重ねてUpgradeしていくことが予想されます。その時に、contractのstorageとlogicの部分を分離して管理することで、新しいcontractのversionに切り替えることができるという恩恵を受けることができます。
初期化
OwnedUpgradeabilityProxy
をdeploy- 最初のバージョンのlogic Contractをdeploy
OwnedUpgradeabilityProxy
のupgradeToAndCall
関数を呼び、proxy contract側にlogic contractを登録すると同時に、proxy contract側の状態を初期化
更新
- 前回のlogic contractと同じ変数名の新しいlogic contractをdeploy
OwnedUpgradeabilityProxy
のupgradeTo
関数を呼びlogic contractを更新
適当にハッシュ化したbyte列にimplementation addressを割り当てるようにする。 proxy contract側で定義されたstorage slot
に変数の情報を格納していくイメージです。
//UpgradeabilityProxy.sol
bytes32 private constant implementationPosition = keccak256("org.zeppelinos.proxy.implementation");
function implementation() public view returns (address impl) {
bytes32 position = implementationPosition;
assembly {
impl := sload(position)
}
}
function setImplementation(address newImplementation) internal {
bytes32 position = implementationPosition;
assembly {
sstore(position, newImplementation)
}
}
アップグレードのために必要なデータを保存するために、proxy contractで定義されている非構造化ストレージスロットを使用します。
proxy contractでは、logic contract addressを格納する上で十分にランダムな格納位置を与える定数を任意の文字列をhash化することで定義します。
定数状態変数はストレージスロットを占有しないので、implementationPositionが誤ってproxy contractによって上書きされる心配はありません。 Solidityがどのようにその状態変数をストレージにレイアウトするかにより、logic contractで定義された他のものによってこのストレージスロットが衝突される可能性はほとんどありません。
このパターンを使用することで、どのlogic contractバージョンもプロキシの記憶構造について知る必要はありませんが、将来のすべてのlogic contractは、それらの上位バージョンによって宣言された記憶変数を継承する必要があります。将来アップグレードされるlogic contractでは、既存の機能をアップグレードしたり、新しい機能や新しいストレージ変数を導入することができます。
Zeppelin’s labsリポジトリで提供されているこの実装は、proxy contractの概念も使用しています。proxy contractのownerは、proxy contractのアップグレードや所有権の移転ができる唯一のアドレスです。
sstore
やsload
などのassemblyの仕様は次のリンクを参照。
参考:Solidity Assembly
このproxy contractの最大の利点は、logic contract側は、proxyの一部であることを定義しないくても良いということです。
注意
constructorの機能が失われるのは、ownable
contractとかのconstructorも同様です。logic contractをdeployした時のconstructorで決定される最初の状態だけは、proxy contract側に保存されません。
したがって、logic contractの方で、initialize
関数などを作り最初にきちんとownerを定義する必要があります。あるいは、upgradeToAndCall
関数でUpdate
した時にconstructorに当たる関数を呼び出して初期化する必要があります。
Unstructured Storageは、solidity assemblyを利用して、logic contractのアドレスを保存しているため、logic contract側でupgradeablityなどのコントラクトを継承する必要がないところが優れています。また、owner権限の移譲ができる他、新しい変数を定義してstorageに入れておくこともできます。
サーバーサイドの構成について
MOLDEX αでは、サーバーレスアーキテクチャーを採用し、主に静的ファイル処理、API処理、バッチ処理の3つの処理を行なっています。
静的ファイル処理
静的ファイル処理は 、js ファイルや画像ファイルの格納先として利用しており、CDNとしてのCloudFrontと静的ウェブサイトのホスティング用としてのS3 のみで構築されています。
CloudFront を利用することでキャッシュによる処理速度向上だけでなく、AWS Shield が有効になり DDoS 保護が有効なります。また、S3 だけでは独自ドメインによる SSL/TLS プロトコルの利用はできませんが、CloudFront を利用することで利用可能になります。
上記の通り、CloudFront を利用すると様々なメリットを享受できるため、S3 に直接アクセスさせずに CloudFront を利用した構成を採用しています。
API 処理
ECS では、アプリケーション、サービス、バッチ処理を実行する Docker コンテナを簡単にデプロイ、管理、スケーリングすることができます。コンテナ運用をするため、APIの変更時などもアプリケーションの可用性が維持されます。
また、現在は同時に多くのrequestを処理することが必要になることはありませんが、今後アクセスが集中する場面などでアプリケーションを簡単にスケールすることが可能になります。
Ethereumへのアクセスは、フロントからは行わず、全てAPIを通して行うことで、フロント側での余計な処理を減らすようにしました。
バッチ処理
例えば、trade関数を実行した後の取引履歴などは、EthereumのブロックチェーンでTransactionのEventを参照することで、最新の状態を常に保つようにしています。
また、approveされていたアセットが他のアドレスに所有権が移った場合には、Dex Contractへのapproveは無効になってしまいます。したがって、一定間隔で、Ethereum側にallowanceの監視をする必要があります。
CI/CD環境
よくある構成図になりますが、circleCIからBuild→test→deployの処理が走るようにしたことで、比較的スムーズに開発を行うことができました。APIの部分は、今回は、Golangを使って記述しましたが、Geth(go-ethereum)の実装を参考にする部分が多々ありました。
ブラウザwalletについて
現在、Ethereum上のほとんどのDappsがMetamaskを利用していますが、MOLDEX αでは、あえてMetamaskは使わずに、フルスクラッチでアプリ内蔵のブラウザwalletを実装しました。意図としては、ブロックチェーンについて詳しくないユーザーでも使えるDEXを意識したことがあげられます。
ブロックチェーンについて多少詳しいユーザーだと当たり前のようにchromeの拡張機能に追加していると思いますが、大半のユーザーは、metamaskはおろか、拡張機能さえ使ったことがないというのが現状です。
DEXをはじめとするDappsの理想は、ユーザーにそれがブロックチェーンを使っているのがわからないほど、スムーズで無理のないUXを実現することにあると思います。一つのDappsを使うのに、必ずMetamaskをインストールしないといけないといった(大半のユーザーにとっては)煩雑な手続きをしないで済むやり方として、walletの機能をアプリケーションに内蔵するという方法が考えられます。MOLDEX αでは、まだまだ荒削りながら、ブラウザwalletを内蔵することで、一度ログインしたユーザーは、passwordだけで、アプリを使えるようになる仕様になっています。
秘密鍵の管理
DEXでは、秘密鍵の管理は、ユーザー側で行い、サーバー側ではユーザーの秘密鍵の情報は一切持ちません。
初めてMOLDEXにアクセスするユーザーは、create key
で新しくアカウントを生成するか、既存のEthereumのwalletをimportします。
MOLDEX αでは、ユーザーの秘密鍵は、sessionで管理、暗号化されたjson形式のkey fileはブラウザのlocal storageで永続的に管理しています。
sessionで管理している秘密鍵は、ユーザーがページを離れると(例えばタブを消すなど)、削除される仕組みになっているので、再びアクセスするとlocal storageに保存された暗号化されたkey fileをunlockするために、passwordの入力が求められます。
一方、local storageで管理されている暗号化された key fileは、ユーザーがページを離れてもlocal storageに保存されるので、ユーザーが再度アクセスした際には、ユーザーはpasswordを入力してunlockするだけで、アプリケーションを利用することができます。
一度walletをunlockすると、sessionが切れるまでは、ユーザーは、自由にアプリケーションを使うことができます。これは、毎回Metamaskのポップアップが現れて承認するといった煩雑な手続き(しかも遅い!)を簡略化してくれます。
また、ブラウザに、暗号化されているものの、key file情報を残しておきたくないといった場合には、delete key
をすることで、local storageからもkey fileの情報を消すことができます。その場合は、再度アクセスするときには、import key
をする必要があります。
実際に、MOLDEX αを利用するときは、key fileをexportしてクラウド上や安全な所に保管して置くようにしてください。
DEXの恩恵
DEX概要:中央集権的な取引所との違いや特長について[PART 1]で見たように、DEXの特長として、
- プライバシーが守られる
- セキュリティが高い
- 操作の可能性が低い
という3点があげられますが、実際にこのような特長がMOLDEX αにあるのか見ていきましょう。
MOLDEX αでは、誰でもアプリケーション上でアカウントを作り、取引を開始することができます。このとき、サーバー側で、ユーザー情報を管理しているわけではないので、完全に匿名での取引が実現されています。
また、ユーザーの秘密鍵はユーザーが自分で管理しているので、たとえ、万が一サーバー側がハッキングされたとしても、ユーザーの資産(アセットやmoldcoin)を勝手に動かすことはできません。
さらに、注文に祭して、必ず秘密鍵で署名をして、Dex Contractで検証をするので、サーバー側で不正に注文を操作するといったこともできません。したがって、ユーザーは、MOLDEX α上において、トラストレスで(サーバー側を信用しなくても大丈夫な状態で)取引を行うことができます。
以上の観点から、今回のMOLDEX αでは、MOLDEXの世界観を示すとともに、DEXの持つ利点を具体的に理解することができる有用な例となったと言えるでしょう。
MOLDEXの今後
P2Pでのデジタル資産の自由で迅速な取引をどのようにしたら行うことができるのかということを考えると、一つの手段としてDEX(Decentralized Exchange)という手段が挙げられます。しかし、Ethereumとの連携をはじめとして、DEXはサービス化しようとすると開発コストも高く、想像以上に大変です。また、Ethereumのブロック生成にかかる時間やGas費用は、やはりUXを低下させる一つの大きな要因になります。
今後、MOLDEX α版は、他にも次のような様々な機能を追加してEthereum上でのMOLDのコンセプト実現を目指して行きたいと考えています。
- ERC721のアセットの種類を増やす
- ETHでも交換できるようにする
- ERC1155のアセットの交換をできるようにする
- 送受信の取引履歴を参照できるようにする
- login sign in 機能を追加する
- ゲームページを作る
※ MOLDEX αは、あくまでmoldcoin保有者に向けたテストバージョンになります。今後大幅に仕様が変更になる恐れがありますので、あらかじめご了承ください。
同時に、MOLDでは、ことゲームに関してEtheruemが抱える課題を解決するべく、ゲームに特化した独自チェーンの構築も進めて行きたいと考えています。Ethereum上でのMOLDEXの開発、及び独自チェーンの研究開発の両軸でやっていくことで、変化が激しいブロックチェーン業界で着実にプロジェクトを進めていきます。
募集
現在MOLDでは、一緒にMOLDEX及び独自チェーン開発をより良いサービスとして成長させていけるエンジニアのメンバーを募集しています。興味がある方は、Twitterまたは、team@moldproject.org までお気軽にご連絡ください。
— — — — — — — — — — — — — — — -
Cosmos Gaming Hub Project(旧MOLD Project)
CEO & Co-Founder
朝野 巧己
全てのゲーム愛好家に最高のエンターテイメントを届けるために