Symbol(XYM)のコミュニティでは、「DEX が欲しい」という声をずっと聞く。なんかもうみんな言っている気がする。「スマートコントラクトがないから無理」という声もずっと聞く。これもなんかもうみんなさんざん言っている気がする。
たしかに Symbol にはスマートコントラクトがない。イーサリアムの DEX のように、プールのコントラクトがお金を預かって、AMM が勝手に交換してくれる、という形は作れない。じゃあ誰かが預かって交換するのかというと、それは取引所であって DEX ではない。
と、思っていたのだが、「なんかそれっぽいものは」あっさり作れてしまった。Symbol の規則には一切手を入れていない。Ethereum Virtual Machine も Inter-Blockchain Communication も何もない、GitHub や DockerHub にあるふつうの Symbol のノード(catapult)も、そのブロックをふつうに受け入れた。でもこれを実運用にのせるにはさすがに無理やりに感じる。それでも、誰にも預けない交換が、一応申し訳程度にはちゃんとアトミックに成立はする。おいおいまじか😆
自作の heika(Symbol の Rust のツールキット。試験的にノードも動く)の使用例として置いている。つまりこれは heika のライブラリ群のユースケースを紹介する記事である。
なにが難しかったのか
Symbol には、アグリゲートトランザクションがある。いくつかのトランザクションを 1 つにまとめて、全部通るか、全部通らないかにできる。交換に使うなら、こうなる。
- A → B に TOKEN を 50
- B → A に XYM を 1000
これを 1 つのアグリゲートにして、A と B の両方が署名すれば、交換は一度に成立する。どちらかが持ち逃げすることはない。
問題は、署名がアグリゲートの中身全体にかかることだ。中身には相手のアドレスが入る。つまり、相手が決まらないと署名できない。「誰でもいいから、この値段で TOKEN を売ります」という、開いた注文を署名済みの形で置いておけないのである。
開いた注文が置けないなら、相手が来るたびに売り手がオンラインで署名するしかない。それは DEX というより、相対取引だ。
発想その 1: 秘密鍵を公開したアカウントを相手にする
相手が決まらないなら、誰でも署名できる相手を用意すればいい。
注文ごとに、新しい鍵 P を作る。そして、その秘密鍵を公開してしまう。売り手(メイカー)は、P を相手にした注文に署名する。
- メイカー → P に、出すもの(TOKEN を 50)
- P → メイカーに、欲しいもの(XYM を 1000)
- メイカー → P に、約定させてくれた人へのスワップ手数料(0.1 XYM)
これ、スワップ手数料引いてから送ればよかった。あーあ🙄
P の署名(連署)は、鍵が公開されているので誰でも付けられる。これで、相手が決まっていないのに、メイカーの署名が済んだ注文ができた。
……ただ、このままだと困る。P に XYM を入れておかないと 2 が通らない。でも P の鍵は公開なので、P に入れたものは誰でも持っていけてしまう😇
発想その 2: トランザクションではなく、ブロックで原子性を担保する
ここで視点を変える。交換を 1 つのトランザクションの中で閉じようとするから、行き詰まる。たぶん今までの挑戦者はみんなこれが原因で挫折したのではないだろうか。そりゃそうだと思う。
ブロックを作る人(ハーベスター)は、自分のブロックの中のトランザクションの並びを、自分で決められる。しかも、ブロックは中のトランザクションが順に全部通らないと、ブロックごと無効になる。
なので、ハーベスターが自分のブロックに、こう並べる。XYM を出すのは、ハーベスターが用意したアカウント(以下、在庫)だ。
1. 在庫 → P に XYM を 1000 (貸す)
2. メイカーの注文(P の連署を付ける) (交換)
3. P → 在庫に TOKEN を 50 とスワップ手数料(回収する)
P に XYM が置かれているのは、このブロックの中の、ほんの一瞬だけだ。並びはハーベスターが決めるので、そのあいだに他人のトランザクションが割り込むことはできない。途中で失敗するなら、そもそもブロックにならない。
結果として、メイカーは TOKEN を 50 出して XYM を 1000 受け取り、在庫は XYM を 1000 出して TOKEN を 50 とスワップ手数料を受け取る。誰も、誰にも預けていない。
ブロックを一続きの処理として使えば、ハーベスターが約定させる役(イーサリアムでいうソルバー)になれる。
注文はどこに出すのか
注文は、決まったアドレス(掲示板)あての送金のメッセージに入れて、チェーンに出す。中身は、P の秘密鍵と、署名済みの注文のバイト列だ。モザイク 1 種類どうしなら 493 バイトで、メッセージの上限(1023 バイト)に収まる。
掲示板のアドレスは、決まった文字列のハッシュを公開鍵とみなして作る。対になる秘密鍵は誰も知らない。なので、掲示板に受け取りの制限をかけて止められる人もいない。
チェーンの外に注文の置き場を作る案も考えた。でも、誰でも読めて、誰にも消せない場所に注文があることのほうがだいじだと思って、チェーンにした。掲示板に出すトランザクション手数料は、倍率 100 で 0.065 XYM くらいである。約定したときには、注文のアグリゲートのトランザクション手数料もメイカーが払う(P の連署 1 つ分も見込んだ額)。
誰が誰を信じるのか
- メイカー: 誰も信じなくていい。注文は署名したとおりにしか動かない
- ハーベスター: 誰も信じなくていい。自分で組んで確かめたブロックを出すだけだ
- XYM を貸す人: ここだけは注意がいる。ハーベスター本人が貸すなら問題ない。でも、ほかの人が貸すなら、「ハーベスターが貸すところだけをブロックに入れて、残りを入れない」ことをしないと信じるしかない
なので、貸すのはハーベスター本人の在庫にした。サーバーに在庫の鍵を置くことになるが、危ないのは運営する人自身のお金だけだ。ハーベストの鍵とは分けて、置く額には上限をかける。
P が乗っ取られたら?
P の鍵は公開なので、誰でも P をマルチシグにしたり、制限をかけたりできる。そうされると、P の連署が通らなくなり、その注文も通らなくなる。
前もって防げないかも考えた。でも、catapult のソースを読むかぎり、どちらもだめだった。
- 操作の制限で P の操作を縛っても、「制限を変える」操作そのものは禁止できない作りになっている
- 先に P をマルチシグにして、連署人を外すには全員の承認が要るようにしても、その前に「外すのに要る数」のほうを、ふつうの承認の数で下げられてしまう
ただ、乗っ取られて起きるのは「その注文が取り消される」ことだけで、お金は盗られない。P は注文ごとに変えるので、嫌がらせで注文を消すには、注文の数だけトランザクション手数料を払うことになる。ここは割り切った。だっていい方法思いつかないんだもん……🥺
プライベートネットで試した
catapult 2 台と、この例のノード 1 台のプライベートネットで動かした。
- 掲示板に出した注文を拾って、heika ベースのノードが自分のブロックで約定させた。ブロックの中は、貸す、注文、回収するの 3 つだった
- catapult の 2 台は、そのブロックをそのまま受け入れた。そのあとも同じチェーンで進んだ
- 残高はぴったり合った。メイカーは TOKEN が 50 減って、XYM が 1000 からスワップ手数料の 0.1 を引いた分だけ増えた。在庫はその逆。P には何も残らなかった(ブロックの倍率が 0 だったので、トランザクション手数料はかからなかった)
- わざと、持っている量より多く出す注文も出してみた。ノードは約定させようとして弾かれ、そのたびに約定なしのブロックを作り直した。3 回であきらめて、チェーンは何事もなく進んだ
やったー🎉わーいわーい✨
本番に使えるか
ここは正直に書いておく。今のままでは、本番には足りないものがある。
- 約定する機会が少ない。 約定させられるのは、この作りのハーベスターだけだ。heika がブロックを作る番が来るまで、注文は待たされる。いちばんの壁はここである
- 誰でも、トランザクション手数料を払えば注文を消せる。 盗まれはしないが、本気の嫌がらせには弱い
- 全部約定するか、何もしないか。 一部だけの約定はできない。注文の期限も最長 6 時間なので、長く出すなら出し直しが要る
- 在庫の値動きと、オンラインの鍵の危険は、ノードが負う
- 試作として割り切ったところもある。 1 つのブロックで約定させるのは 1 つの注文だけで、掲示板を読み直すのも 10 秒ごとだ
- 値段の差を誰が取るかの決まりがない。 逆向きの注文どうしを突き合わせたときの差は、ハーベスターの総取りになる
- XYM以外に金目のものが見当たらない
それでも、「Symbol の規則を変えずに、誰にも預けない交換が成り立つ」ことは示せたと思う。形としては、インテント型の DEX(使う人は「これを出して、これが欲しい」を署名して出すだけで、約定させるのはソルバー)の仲間だ。ソルバーを、ハーベスターが兼ねている。
いちばんの壁は、heika でハーベストする人が増えれば、そのまま低くなる。そうなったら、本番の候補として無理を承知で受け入れられるかもしれない。
おわりに
「Symbol にはスマートコントラクトがないから DEX は無理」と、私も思っていた。でも、無理だったのは「トランザクションで完結する DEX」だった。ブロックを作る人が並べ方を決められる、というごく当たり前の常識を使えば、絶対的な「無理」ではなかった。ビジョンが狭いと損をするいい例だと思い知った。
みんなが欲しがっている DEX に、ちょっとだけ、ほんのちょーーーとだけ、近づけた気がする。