🙄

Solanaでプログラムを使った計算結果を取得する

2023/10/27に公開

Solangを読んでいたら不思議なコードに出会いました。

/// Simply returns the current value of our `bool`.
function get() public view returns (bool) {
    return value;
}

Solanaのプログラム(スマートコントラクト)で値を取得する???

プログラムはデータをアカウントに書き込むために使用するのであって、Read用途では使えない認識でした。(アカウントに書き込む、トランザクションログを出力する、成功/失敗のステータスコードを返すだけできる認識)

SolangはSolidityで書けるようになるだけであり、基本的にはSPLで書かれているコードと同じような実行をするはずです。
そこで、コードを追いかけてどう実現しているのか調べてみましょう。

solana-developers/program-examples

https://github.com/solana-developers/program-examples

solana-developersのprogram-examplesを見るといくつかSolangの例があります。

https://github.com/solana-developers/program-examples/blob/main/basics/account-data/solang/

今回はその中のシンプルなアカウントを扱うaccount-dataを動かしていきましょう。(SolangのInstallationは完了している前提です)

IDLを確認する

$ anchor build

上記コマンドを実行するとtarget/以下にいくつかファイルが生成されます。
その中のIDLを確認してみましょう。

target/idl/account_data.json
{
  "version": "0.0.1",
  "name": "account_data",
  "instructions": [
    {
      "name": "new",
      "accounts": [
        {
          "name": "dataAccount",
          "isMut": true,
          "isSigner": true,
          "isOptional": false
        },
        {
          "name": "payer",
          "isMut": true,
          "isSigner": true,
          "isOptional": false
        },
        {
          "name": "systemProgram",
          "isMut": false,
          "isSigner": false,
          "isOptional": false
        }
      ],
      "args": [
        {
          "name": "space",
          "type": "u16"
        },
        {
          "name": "name",
          "type": "string"
        },
        {
          "name": "housenumber",
          "type": "u8"
        },
        {
          "name": "street",
          "type": "string"
        },
        {
          "name": "city",
          "type": "string"
        }
      ]
    },
    {
      "name": "get",
      "accounts": [
        {
          "name": "dataAccount",
          "isMut": false,
          "isSigner": false,
          "isOptional": false
        }
      ],
      "args": [],
      "returns": {
        "defined": "AddressInfo"
      }
    },
    {
      "name": "getAddressInfoSize",
      "accounts": [
        {
          "name": "dataAccount",
          "isMut": false,
          "isSigner": false,
          "isOptional": false
        }
      ],
      "args": [],
      "returns": "u256"
    }
  ],
  "types": [
    {
      "name": "AddressInfo",
      "type": {
        "kind": "struct",
        "fields": [
          {
            "name": "name",
            "type": "string"
          },
          {
            "name": "houseNumber",
            "type": "u8"
          },
          {
            "name": "street",
            "type": "string"
          },
          {
            "name": "city",
            "type": "string"
          }
        ]
      }
    }
  ],
  "metadata": {
    "address": "eMzAsf1MokLZEZVVe7NpJyJLW2F15bME7sYrr3XMJJK"
  }
}

Anchorでプログラムを作成したさいと同様にInstructionの定義が書かれたIDLが生成されました。
つまり、これはSolang独自の何かでではなく、Solanaのプログラムとしてデータを取得する方法があるということです。
(本来は.soのリバースエンジニアリングで〜とかで確定すべきですけど、その元気はなかった)

テストコードから解析する

今度はIDLを利用するクライアント側から見てみましょう。

https://github.com/solana-developers/program-examples/blob/main/basics/account-data/solang/tests/account-data.ts#L42-L51

ここで呼び出しているview()で値の取り出しを行っているようです。
このviewメソッドはanchor側に定義されています。

https://github.com/coral-xyz/anchor/blob/5a655b0f65db00d626901cb200068484b1454d37/ts/packages/anchor/src/program/namespace/views.ts#L23-L45

このメソッドは下記の流れで実行されます

  1. simulateFnを実行する
  2. 戻り値でProgram return: ${programId} から始まる行を取得する
  3. 2の対象の行をデコードして、IDLで定義した型に変換する

https://github.com/coral-xyz/anchor/blob/master/ts/packages/anchor/src/program/namespace/simulate.ts#L27-L65

simurateFnではprovider!.simulateが呼び出されており、

https://github.com/coral-xyz/anchor/blob/5a655b0f65db00d626901cb200068484b1454d37/ts/packages/anchor/src/provider.ts#L284-L328

最終的にconnection.simulateTransactionが呼ばれる形になります。
つまり、simulateTransactionを実行するとプログラムの実行ログが出力され、それを取得してデコードすることで、プログラムで計算した結果を取得できるようになるということですね!

おわりに

強い。(確信)

たぶん、これがSolangじゃなければトランザクションのログはログなので、そう使うものじゃないんだよ?と言ってしまいそうになるぐらいには強さに満ち溢れていますね。
手法としては面白いし、何かで活用できる気はするのですが、多用していいかは少し迷います。

Discussion