🤸‍♂️

Solang (Solidity)で始めるSolanaスマートコントラクト開発

2023/10/29に公開

https://github.com/hyperledger/solang

SolangはSolidity v0.8互換のSolana、Polkadot向けに拡張されたコンパイラです。
こちらを使うことでSolidityを使ってSolanaのプログラム(スマートコントラクト)の生成を行えるため、EVMでの開発に慣れている方は比較的容易にSolanaの開発をできるようになっています。

https://solang.readthedocs.io/en/v0.3.3/targets/solana.html

Solanaのプログラム向けにコンパイルするさいに、通常のSolidityと違う点がいくつかあるため詳細については上記を参照してください。

インストール

https://solang.readthedocs.io/en/latest/installing.html

MacOS
$ brew install hyperledger/solang/solang
$ solang --help
Solang Solidity Compiler

Usage: solang <COMMAND>

Commands:
  compile          Compile Solidity source files
  doc              Generate documention for contracts using doc comments
  shell-complete   Print shell completion for various shells to STDOUT
  language-server  Start LSP language server on stdin/stdout
  idl              Generate Solidity interface files from Anchor IDL files
  new              Create a new Solang project
  help             Print this message or the help of the given subcommand(s)

Options:
  -h, --help     Print help
  -V, --version  Print version

SolangはMacOS環境ではbrewを使ってインストール可能です。
他環境向けのインストールではリンク先を参照してください。

Solana公式サイトのGetting Startedではsolana-cli、anchorのインストールが求められますが、これはanchorのSolangサポートを利用しているためです。

プロジェクトの作成

https://solang.readthedocs.io/en/v0.3.3/running.html#starting-a-new-project

$ solang new --target solana my_project
$ tree -r
.
└── my_project
    ├── flipper.sol
    └── solang.toml

2 directories, 2 files

solang newコマンドで新しいプロジェクトを作成できます。

solang.toml
[package]
version = "0.1.0"

# Source files to be compiled.
input_files = ["flipper.sol"]

# Contracts to be compiled.
# If no contracts are specified, solang will compile all non-virtual contracts.
contracts = ["flipper"]

# Specify required import paths.
import_path = []

# Define any importmaps. 
# import_map = { "@openzeppelin" = "/home/user/libraries/openzeppelin-contracts/" }
import_map = {}


[target]
name = "solana"

[debug-features]
# Log debug prints to the environment.
prints = true

# Log runtime errors to the environment.
log-runtime-errors = true

# Add debug info to the generated llvm IR.
generate-debug-info = false

[optimizations]
dead-storage = true
constant-folding = true
strength-reduce = true
vector-to-slice = true
common-subexpression-elimination = true

# Valid LLVM optimization levels are: none, less, default, aggressive
llvm-IR-optimization-level = "aggressive"

[compiler-output]
verbose = false

# Emit compiler state at early stages. Valid options are: ast-dot, cfg, llvm-ir, llvm-bc, object, asm
# emit = "llvm-ir" 

# Output directory for binary artifacts.
# output_directory = "path/to/dir"   

# Output directory for the metadata.
# output_meta = "path/to/dir" 

# Output everything in a JSON format on STDOUT instead of writing output files.
std_json_output = false
flipper.sol
contract flipper {
	bool private value;

	/// Constructor that initializes the `bool` value to the given `init_value`.
	@payer(payer)
	constructor(bool initvalue) {
		value = initvalue;
	}

	/// A message that can be called on instantiated contracts.
	/// This one flips the value of the stored `bool` from `true`
	/// to `false` and vice versa.
	function flip() public {
		value = !value;
	}

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

Anchor IDLからのSolidityコードの生成

https://solang.readthedocs.io/en/v0.3.3/running.html#generate-solidity-interface-from-idl

SolangではanchorのIDLからのSolidityコードの生成もサポートされています。

$ solang idl account_data.json 
account_data.json: info: creating 'account_data.sol'
account_data.sol
struct AddressInfo {
	string	name;
	uint8	houseNumber;
	string	street;
	string	city;
}
@program_id("F1ipperKF9EfD821ZbbYjS319LXYiBmjhzkkf5a26rC")
interface account_data {
	@selector([0x87,0x2c,0xcd,0xc6,0x19,0x01,0x48,0xbc])
	function initialize(uint16 space,string name,uint8 housenumber,string street,string city) external;
	@selector([0xa1,0xe0,0x32,0x3d,0x05,0xd2,0x7a,0xd8])
	function get() view external returns (AddressInfo);
	@selector([0xa5,0x09,0x3b,0xf0,0x30,0x77,0xd7,0x73])
	function getAddressInfoSize() view external returns (uint256);
}

IDLの元は下記のコードになります。

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

コンパイル

https://solang.readthedocs.io/en/v0.3.3/running.html#compiler-usage

コンパイルを実行するとsolanaのプログラムファイルである.soと、anchorのIDLが生成されます。

$ solang compile
$ ls -la
total 240
drwxr-xr-x  6 ab  staff     192 10 29 12:06 .
drwxr-xr-x  7 ab  staff     224 10 29 11:55 ..
-rw-r--r--  1 ab  staff    1494 10 29 12:06 flipper.json
-rw-r--r--  1 ab  staff  109312 10 29 12:06 flipper.so
-rw-r--r--  1 ab  staff     513 10 29 11:46 flipper.sol
-rw-r--r--  1 ab  staff    1310 10 29 11:46 solang.toml
flipper.json
{
  "version": "0.1.0",
  "name": "flipper",
  "instructions": [
    {
      "name": "new",
      "docs": [
        "notice: Constructor that initializes the `bool` value to the given `init_value`."
      ],
      "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": "initvalue",
          "type": "bool"
        }
      ]
    },
    {
      "name": "flip",
      "docs": [
        "notice: A message that can be called on instantiated contracts.\nThis one flips the value of the stored `bool` from `true`\nto `false` and vice versa."
      ],
      "accounts": [
        {
          "name": "dataAccount",
          "isMut": true,
          "isSigner": false,
          "isOptional": false
        }
      ],
      "args": []
    },
    {
      "name": "get",
      "docs": [
        "notice: Simply returns the current value of our `bool`."
      ],
      "accounts": [
        {
          "name": "dataAccount",
          "isMut": false,
          "isSigner": false,
          "isOptional": false
        }
      ],
      "args": [],
      "returns": "bool"
    }
  ]
}

コンパイルで出来た成果物からsolana-cliを使用してプログラムをデプロイしたり、IDLからクライアントコードの生成を行います。
anchorを使うとanchorコマンドで統合して扱うことができます。開発・運用スタイルに合わせてanchorを採用するか、コマンドを組み合わせて使うか検討することをお勧めします。

おわりに

Solidity自体はシンプルな言語なので、Solangを使うことでSolanaのプログラム開発の敷居の高さはかなり低くなります。
特に日本でのブロックチェーン開発ではEVM系が一般的でSolidityユーザーは多いので、Solangを採用してSolanaのプログラムの開発をするのは悪くない選択肢だと思います。

では、従来のNative (SPL Style)/Anchorを使った開発はしない方がいいかというと、そうでもありません。
特に私のようにRustに慣れているがブロックチェーン開発には慣れていないユーザーからすると、Nativeでの開発なんかは読めばわかる。テストなんかもRustで記述できるというのはとてもやりすいです。
(Solana開発で試したてないし、試した話も見かけないですがFormal Methods系のツールもあったりするし)

そのため、Solangを採用するかは開発チームの状況に合わせて決定するのが良いと思います。

関係資料

Discussion