Solana Tx解体真書~Solscanを日本一のしつこさで解説してみる~後編

 こちらが前編です。

 後編では、少しだけコードも出てきますが、プログラムに明るくない方でも大丈夫です。
 では続きです。


  • 参照元のTx


"Overview"‐Instruction Details

 ここが一番、「わけわからんがなんか色々やっとるなー」となりがちなところですが、"Instruction" "CPI" "PDA"の概念を用いれば理解も容易いはずです。
 上述したTxを参照しながら解説していきます。

#1 - Compute Budget: Set Compute Unit Limit
#2 - Compute Budget: Set Compute Unit Price
#3 - SetTokenLedger
#4 - CykuraSwap

 #1~#4までのInstructionが大きな括りとして表記されています。
 これはこのTxが直接呼び出したInstructionが4つあるということを意味します。つまりTxは1つでも、複数個のInstructionを含めることができるということですね。

#1 - Compute Budget: Set Compute Unit Limit
#2 - Compute Budget: Set Compute Unit Price

 まず、このTxの主目的であるWSOLをUSDCにSwapする前に、上述のInstructionが呼び出されています。
 どうしてこんなものが必要なのでしょうか?

 先述したように、SolanaのFeeは0.000005SOLの基本額が固定で設定されています。
 他チェーン(あまり詳しくありませんが)のように変動しない代わり、これを実現するため、すべてのTxには計算リソース(CPU使用量)の上限が割り当てられています。

 デフォルトではこの上限は200000 Compute Unitという単位で制限がかかっており、Comput BudgetのInsturctionを呼び出さずにTxを送信し、この制限をオーバーしてしまった場合、そのTxは失敗します。
 
 そのためTxの送信側が、普通の使い方より複雑なことさせると前もって把握している場合、Compute Budget: Set Compute Unit Limitによって、「デフォルトじゃ足らないし、これくらいはCPU使わせてもらうかも~」と通知する必要があります。

 しかし同時に、Feeが収入源であるバリデータ側はバリデータ側(Txを処理する人)で、利益を最大化したいという都合があります。
 もらえるFeeが固定ならば、自分が担当するBlockを割り振られた際に、CPU使用量が少ないTx(計算が早い→たくさんさばける→収入↑↑)だけをやって、めんどくさいのは後のBlockに丸投げした方が効率がいいですよね。

 そこでTx送信側はCompute Budget: Set Compute Unit Priceを設定することでその折衷案を提示します。
 この折衷案というのがFeeの上乗せになります。 

 OverViewでFeeの項目をもう一度確認しておきます。 

 そしてこちらがTxが提示したCPU使用量の予約とFee上乗せへの提示です。計算してみます。
 200000のCPU使用量に対して、単価1073Micro LamportsのFeeを提示しているので、計算式は以下になります。

200000  * 1073 = 214,600,000 (MicroLamports)

1MicroLamports = 0.000001Lamports
214,600,000ML * 0.000001 = 214.6Lamports

1Lamports = 0.000000001SOL
214.6 Lamports * 0.000000001 = 0.0000002146 SOL

基本Fee = 0.000005 SOL
0.000005 + 0.0000002146  = 0.0000052146 SOL
                         1Lamports = 0.000000001//最小単位

最小単位から端数を切り上げ
最終Fee = 0.000005215 SOL            

 一致しました!(パチパチパチ)

 さて、これで次の#3のInstructionへ移ってもいいのですが、

  •  Interct With

  •  Instruction Data

  •  Input Accounts

 これらの各項目も解説しておきましょう。

  • Interact With

 Interact Withはユーザーが直接呼び出したProgramです。
 ”SOL Balance Change”のタブで渡したAccount一覧を見ると、"ComputeBudget1111…."には”Program”の属性が付いていることが確認できます。


  • Instruction Data

 見ての通りこのままだと理解不能です。
 くるくるマークにマウスオーバーすると”hex data”などと切り替えられるようなので、hex(16進数)をデコードしてみます。

hexをbytesに→bytesをBufferに

 PythonでBuffer(1バイトずつ分ける)に直してみました。
 [ 2, 64, 13, 3, 0]という5バイトの5つの数字が得られます。これでもまだ意味不明なので、ComputeBudgetが定義されているコードをのぞいてみます。すると、このような記述が見られました。

ソース:https://github.com/solana-labs/solana/blob/master/sdk/src/compute_budget.rs

  赤字で”enum”とあるのは列挙型と呼ばれるもので、目録のことです。

 プログラミングが分からなくても問題ありませんが、このComputeBudgetというProgramが四種類のInstructionに対応していて、それぞれの要求に応じて分岐している、ということがここでは示されています。
 Set Compute Unit Limitは上から三番目に、Set Compute Unit Priceは上から四番目に位置しています。
 
 プログラミングでは、0から数えるのがセオリーですので、Set Compute Unit Limitは[2]、Set Compute Unit Priceは[3]に該当します。

 [ 2, 64, 13, 3, 0]の最初の[2]はSet Compute Unit Limitを使用すると明示しているようです。
 残るは[64, 13, 3, 0]の4バイトですが、コードのSet Compute Unit Limitの( )の中にu32とあります。4バイトは、1ビット=8バイトから4*8=32ビットなので、この4つで数字ひとつを表していることになります。
 Pythonで変換してみます。

変換した結果
Solscan上の表示

 一致しました!
 同じ手順でSet Compute Unit Priceの方も検証してみましょう。

 hexを1バイトずつに仕分けすると、
 [3, 49, 4, 0, 0, 0, 0, 0, 0]と、今度は9バイトの数字が得られました。[3]はSet Compute Unit Priceの使用を表しているので、残りの8つの数字を1つの整数に変換しました。Set Compute Unit Priceの( )内が、今度はu64になっているので8バイト使用します。

 こちらも一致しています。まとめると以下のようになります。

Instruction Data
 ProgramがInstructionを実行する際に必要となる。
 Account(オンチェーン上に存在するデータ)としては渡せないものを用意し、一塊のデータとして変換したもの

Input Accounts
 Insturctionで渡したAccountに加えて、Instruction Data内から使用した主要なもののリスト

 ComputeBudgetでは、"200000"や"1073"といった、ユーザーが設定する数値以外を必要としないため、Input Accountsの欄は1つだけでした。
 Accountだけを渡して、数値等を渡さないパターンもあるので、次の#3 - SetTokenLedgerも確認してみましょう。


#3 - SetTokenLedger

 はじめに白状すると、このSetTokenLedgerが具体的に且つ厳密に何をしているのか、僕は知りません。
 
 というのも、Jupiter Aggregator V3というオンチェーンにデプロイされたProgramのコードが公開されていないために知りようがないからです。
 
 しかし何のために必要なのか何をやっているのか、は分からずとも、どうやって使うか何を使っているかはTxを見ることで把握できます。

 ついでなので、Instruction Dataをデコードしてみます。

 [228, 85, 185, 112, 78, 79, 77 ,2]という8バイトの羅列が得られましたが、これらにはProgram内のどのInsturctionを使用するかという指定以外に意味はありません。
 というのも、Jupiter Aggregator V3はAnchorと呼ばれるフレームワーク(Programのテンプレートのようなもの)によって作成されており、このAnchorによって作製されたProgramはInstruction Dataの最初の8バイトに識別子(ID)を含めるという共通ルールを持っているためです。
 このInstruction Dataは8バイトちょうどであるため、識別子以外のデータは渡していないということになります。

 InputAccountsも見てみましょう。

 CUNMで始まるAccountと、2wLdGで始まるAccountのふたつをProgramに渡しています。

 "CUMN……"はJupiter Aggregator v3が管理するPDAで”2wLdG”はユーザーのUSDCトークンAccountのようです。
 ただし、"Signer"属性となるAccountを渡していないことから、ユーザーのUSDCトークンAccountを(減らす方向に)書き換えることは不可能なはずです。

tokenAccount にはisMut(Writable)が付与されていない

 "2wLdG"は実際にはここでは”Writable”は渡されていないことが、”Anchor Program IDL”のタブでも確認できますが、後のInstructionで"Writable"として渡しているために重複分として上書きで表記されているだけのようですね。

SetTokenLedgerをまとめみてます。
・Interact With
  → Jupiter Aggregator V3というProgramを呼び出した。
・Instruction Data
  → Anchor製のため、その識別子である8バイトのみ。
・Input Accounts
  → Jup-v3が所持するPDA(Writable)
      ユーザーのUSDCトークン(Writable)
               *ただし、このInsturction単体ではReadOnly

上記から分かること
  → Signer属性のものは渡していない
  → CPIで他のProgramを呼び出してもいない。

 SetTokenLedgerが何をしているのかはわかりませんが、JupiterのDiscordをのぞいてみると、このような言及がありました。

トークン台帳は、1つのスワップから別のスワップへのスワップ量を記録するために使用されます。

 とはいえ、本当の本当の本当にそうなのか、はProgramのコードが公開されない限りは裏付けできませんし、さらに言えば、公開されていたとしても実際にデプロイしているものとは中身が違う、さらにさらに言えば、第三者が勝手にアップデートで書き換えた、なんてこともあり得なくはないということも頭の片隅に入れておきましょう。


 とはいえ、ヤバいことをしていないかどうか、はTxを見ることで確認できたので次へ行きましょう。

#4 - CykuraSwap

 一気に長大になりましたが、しかしもはや真新しいものはInner Instructions(CPI)だけです。
 その前にまずはInstruction Dataの中をのぞいてみます。

hexを1バイトずつに仕分けた結果

 これまで見てきたInstructionDataよりも難解ですが、解析は可能です。こうなりました。

解析結果
参照できるデータ

 ちゃんと対応しているようです。
 Anchor製の場合、下のキャプションのようにIDLと呼ばれるProgramの型紙のようなものが用意されている場合もあります。

 なんとなく察した方もおられると思いますが、InstructionDataを分析することで、誰かがこっそりアップしたProgramも勝手に使うことができます。
 危ないですので、絶対にやめましょう!

Input Accounts

 さて、Input AccountsもInsturciton Dataを解析することで下の三つは言質が取れました。(ユーザーが用意して渡したデータということになります)

 ユーザーが直接呼び出したJup-v3 Programは残る#1~#13のAccountも参照してこのInsturctionを処理します。

 ですが、#1のCykura Swapも”SOL Balance Change”のタブで見たように、”Program”属性が付与されています。
 #11のTokenProgramも”Program”属性ですので、同じくProgramであるJup-v3にそれらを渡している格好になります。

 こうすることで、Jup-v3側でもその内部で、ユーザーがJup-v3を呼び出したように、Token ProgramとCykura Swapを呼び出すことが可能となります。
 これがクロスプログラム呼び出し(CPI)と呼ばれるもので、"Innner Instructions”として記述されています。 

 Inner Instructionsは#4.1~#4.4のように表記されています。(つまり4回CPIを行った)
 大括弧内の中括弧といったレイアウトになっているので、白枠内がCPIであることが見て取れます。

CPIだけをピックアップ

 Interact Withで呼び出されているProgramは、Cykura SwapとToken Program2種であり、Jup-v3にAccountとして渡したProgramの数と一致しています。

 ただしよく見ると、子請けのInstructionであるはずのCykura Swapも、TokenProgramをInput Accountsで受け取っていることが確認できます。

 つまり、子請けのCykuraSwapもそこでまたTokenProgramに対してCPIを行っているようです。
 Solscan内では、#4.1~#4.4と同列のように表記されていますが、実態はこうなっています。

 前編パートで述べた、DEXやJup-v3といったProgramは仲介業者に似ているといった雰囲気も感じ取っていただけたのではないでしょうか。(TokenProgramはお役所に近いかも)

 
 さて、ここでは他の各種Accountがどんなデータであるかまでは検証致しませんが、アカウントという言葉に引っ張られたり、アドレスであることを意識せずに、Account=「ただのデータを入れたオンチェーン上に存在する箱」と定義すれば、”TransactionDetails”を理解する上でのノイズは遥かに軽減されたのではないでしょうか。

ノイズキャンセリングOn

 各項目も、
 そのPoolをどこが管理しているのかであったり、
 → Pool State
 どれを減らしてどれを増やすのかであったり、
    → Input//Output Token Account
 その結果をどこに反映させるのかであったりと、
 → Input//Output Vault

 申請書類に必要な欄を埋めるための細々とした数値を、関係各所から取り寄せているにすぎません。
 もちろん書類を提出したあとは、その結果を通達して新しいデータに置き換えなければなりません。
 そうすることで、オンチェーン上に存在するデータ=Accountが刷新され、次に使用する人が同じAccountを参照しても、新しいデータでProgramは次の処理を行うができます。

 もうまとめに入っちゃいるような気もしますが、Program Logがまだ残っているので、さくっとやっつけてしまいましょう。

Program log

#1 Compute Budget instruction

#2 Compute Budget instruction

#3 SetTokenLedger

 これらは特に見どころもないので、

#4 Jupiter Aggregator v3 instruction

 を見ていきます。

ノイズキャンセリングOn

 そのままではノイズが多すぎるので、色が付いているところだけを抜粋しました。
 青字の”Invoking…”で呼び出したCPIの結果が、1対1で緑字と対応しています。

 ・黒 親
 ・橙  子
 ・紫   孫
 ・紫   孫
 ・赤    ひ孫

 このような対応関係になっているようです。
 自分もここを確認するまでは知らなかったのですが、#4.3 SwapCallbackはCykura Swapが自分で自分を呼び出した孫にあたるようです。
 Jup-v3が呼び出したわけではなかったようです。
 そしてその孫の#4.3 SwapCallbackもTokenProgramというひ孫を呼び出し、トランスファーを命令しています。

 このようにProgramlogでは、各CPIの親子関係が確認できます。その他にも、

 Error Message: Slippage tolerance exceeded.
「エラーメッセージです。スリッページの許容値を超えました。」
 とあるように、このTxが失敗した原因も確認できます。スリッページによってTxをはじくかどうかは、最終的にJup-v3が管理していることが、この親子構造から知ることができます。

> Program Jupiter Aggregator v3 consumed 89798 of 196777 compute units

 最初にInsturctionを送ったComputeBudgetが、いくらCPUを使用したか測定している様子も確認できます。
 最終的に89798を使用したようですが、200000の上限を超えていればこのTxは失敗したはずです。
 196777となっているのは、#3 Jupiter Aggregator v3 instructionでも3223ほど使用しており、Instuructionをまたいだからのようですね。

 他にも”Program log: .......…”の細々としたログがありますが、どのような処理を行ったであるとか、その計算結果であったりと、この部分はProgramが独自に定義できるため様々なようです。


まとめ

 いかかでしたでしょうか。
 相当にくどくしつこく解説してきましたが、これで大体のことは説明ができたかなと思います。
 このnoteではSolscanを参照して解説してみましたが、もし気が向いたらSolana ExploereやSolana FMのTxものぞいてみてください。
 もう「なにがなんやら」の項目は、(たぶん)なにひとつないはずです!


この記事が気に入ったらサポートをしてみませんか?