目次
はじめに
Ethereum L2の中でも(執筆時点で)最大の取引量を誇り、大きな注目を集めているArbitrumが、RustやC++などWASMにコンパイルできる言語でArbitrum上にスマートコントラクトを展開できる「Stylus」を発表しました。
これまでEthereumエコシステムでのスマートコントラクト開発は、EVM(Ethereum Virtual Machine)と呼ばれる実行環境で動作するものが主流で、Solidityという専用言語を使用していました。
SolidityはJavaScriptに類似した言語仕様を持っており、その学習コスト自体は特に高いわけではありませんでした。しかし、ガス代を削減するためのコード最適化や、配列やbytesなどの変数の直感的な扱いにくさなど、実際のスマートコントラクト開発においては、EVM由来の特有の扱いにくさがありました。
そのため、Ethereumエコシステムの開発者の間では、WASMで実行可能なRustやC++などの言語を用いたスマートコントラクト開発環境が望まれていました。
本記事ではStylusの概要、使い方、およびSolidityの比較に焦点を当てて解説します。
- Hello, Stylus
- Stylus Now Live — One Chain, Many Languages
ArbitrumのStylusとは
Stylusは、WASM互換言語であるRustやCなどを使用してスマートコントラクトを開発できるプロダクトです。
WASMで実行可能なスマートコントラクトは、ICPやSolanaなどのEVM以外のチェーンで既に実現されていますが、Stylusの革新性は、EVMの実行環境を保持しつつ、WASMの利用を可能にした点にあります。これにより、ブロックチェーン開発で最も多くの開発者が使用している言語の一つであるSolidityのエンジニアは、Arbitrum上での開発を継続できるだけでなく、RustやCといったブロックチェーン外で広く使われている言語の開発者も取り込むことが可能になります。これは、従来のネットワークにはない大きな強みとなるでしょう。
さらに、WASMの導入により、ガスコストの削減や実行速度の向上といったメリットが得られます。特に、Memoryの使用に関しては、Solidityに比べて100から500倍ものコスト削減が見込まれています。
Stylusのメリット
Stylusのメリットは公式ドキュメントで簡潔にまとめられており、それを基に詳細を解説します。
現在はRustのみ対応していますが、将来的にはC、C++、Goなども順次対応予定です。さらに、Fuelで利用されているSway、Aptos/Suiなどで利用されているMove、StarkNetで利用されているCairoなど、他のネットワークで利用されている言語への対応も計画されています。
Solidityの開発者数は約20,000人ですが、Rustは約300万人でCは約1200万人の開発者がいるとされており、Solidityより幅広い開発者層にリーチできる可能性を秘めています。
EVM+
前述の通り、StylusはEVMとWASMの両方を実行できるため、SolidityだけでなくRustやCなどの言語にも対応しています。これにより、最も多いSolidityの開発者を引き続き惹きつける一方で、より多くの開発者がブロックチェーン開発に参入しやすくなります。
Advanced cryptography is now inexpensive
StylusではWASMを用いてスマートコントラクトを扱うことができるため、ガスコストが削減され、処理速度が向上します。これにより、EVMでは扱いにくかった暗号技術関連のライブラリも容易に扱えるようになります。
Opt-in reentrancy
スマートコントラクトのハッキングでよくある手法としてRe-entrancy attackがあります。Solidityの場合は開発者が設計をしてRe-entrancy攻撃を防ぐ必要がありましたが、Stylusの場合はSDKを使って意図的にoverrideされない限りはデフォルトでRe-entrancyを防ぐことができます。
スマートコントラクトのセキュリティリスクとして知られるRe-entrancy attackに対して、Solidityでは開発者が設計段階で防御策を講じる必要がありました。しかし、StylusではSDKを使用することで、意図的にオーバーライドしない限り、デフォルトでRe-entrancyを防ぐことができます。
Fully interoperable
SolidityのプログラムとWASMプログラムは完全に相互運用可能です。つまり、SolidityのコントラクトからWASMのコントラクトを呼び出すことができ、その逆も可能です。
なぜStylusではWASMを実行できるのか
Arbitrumは2022年9月にNitroアップデートを実施しました。このアップデートにより、Stylusを実現するための基盤がすでに構築されていました。
Nitroアップデートでは、Solidityで書かれたスマートコントラクトをコンパイルする際に、EVM用のバイトコードと、証明用のWASMコードの2種類が生成されるようになりました。
バイトコードは従来通り、スマートコントラクトをEVMで実行するために使用されます。
ArbitrumはL2(ロールアップ)ソリューションであり、Arbitrum上のトランザクションが正しいことを証明する必要があります。この証明には「Fraud Proof」と呼ばれる仕組みが用いられています。EVMでFraud Proofを実行するとコストがかかるため、証明プロセスをEVMからWASMに委譲できるようになっています。
ArbitrumやFraud Proofに関する詳細は、関連記事を参照してください。
このように、ArbitrumはNitroアップデートにより、証明のためにWASMを既に稼働させており、EVMとの連携も可能でした。Stylusではこれらの機能を拡張することで、WASMでスマートコントラクトの実行が可能になりました。
Stylusの使い方
StylusのスマートコントラクトはRustの実行環境があれば簡単に作成し、デプロイすることができます。
下記のコマンドを順番に実行すればデフォルトで生成されるスマートコントラクトをデプロイすることができます。
// StylusをWASMの環境をインストール RUSTFLAGS="-C link-args=-rdynamic" cargo install --force cargo-stylus rustup target add wasm32-unknown-unknown // 新しいプロジェクトの作成。サンプルのコントラクトが自動で生成されます cargo stylus new <YOUR_PROJECT_NAME> // 作成したスマートコントラクトがデプロイできるかチェック cargo stylus check // スマートコントラクトのデプロイ cargo stylus deploy \ --private-key-path=<PRIVKEY_FILE_PATH> \
- Stylus Hello World
- Getting Started with Arbitrum Stylus | Developer Tutorials
疑問に感じたことの検証
Stylusの発表を見たときに下記3点が気になりました。
- ① Solidityと比べて、Stylusはどのくらいガスが安くなるのか
- ② Solidityでは扱いに一工夫必要があった配列やbyte(string)は扱いやすくなっているか
- ③ EVMスマートコントラクトとどのように通信できるのか
ここからは実際にスマートコントラクトを作成し、それぞれ検証していきます。
①. ガス代の比較:かなり安い
まずデフォルトで生成されるCounterコントラクトの少し修正して、“increment”を実行するガス代を比較してみます。
SolidiyとStylusのコードはそれぞれ下記のようになっています。
// Solidityのコントラクト contract Counter { uint256 public number; function increment() public { number++; } }
// Stylusのコントラクト sol_storage! { #[entrypoint] pub struct Counter { uint256 number; address executer; } } #[external] impl Counter { pub fn number(&self) ->Result<U256, Vec<u8>> { Ok(self.number.get()) } pub fn increment(&mutself) ->Result<(), Vec<u8>> { let number = self.number.get(); self.number.set(number+U256::from(1)); Ok(()) } }
実際の“increment”の実行結果がこちらになります。
左がSolidityの実行結果、右がStylusの実行結果になります。Arbitrumでの実行の際のガス代はNetworkFeeになりますが、Stylusの方が少し高くなっています。
これはかなりシンプルなコードだったのでそこまで差が出なかったのかもしれません。
したがって、Stylus特にガス代を多く削減できたmemoryを使った適当なコードを作成して比較してみます。下記の関数を追加しました。
// Solidity address public executer; function gasTest()publicreturns(uint256[]memory){ uint256[]memory result =newuint256[](100); for(uint i =0; i <100; i++){ result[i] = i; } executer =msg.sender; return result; } // Stylus pub fn gas_test(&mutself) ->Result<Vec<U256>, Vec<u8>> { let mut result = Vec::new(); for i in 0..100 { result.push(U256::from(i)); } self.executer.set(msg::sender()); Ok(result) }
実行結果は下記のようになりました。
Solidityの実行には0.0000095776 ETHかかっていますが、Stylusでは0.0000061122 ETH ($0.009499 USD)となっており、1/3ほどガス代が削減されています。
Memoryが100~500倍ほど安くなるとドキュメントに書いてあるのでガス代に削減できるかと感じたのですが、1/3程度の削減に収まりました。これは、memory以外にもガスが使用されていることやまだtestnetの段階であることが影響しているかもしれません。
それでも、ガス代が削減されたのは事実で、Mainnetまでにどれだけ最適化されるか楽しみです。
②-1. 配列の扱い方:solidityと同じような制限はある
次に配列に関して調査します。
Solidityでは配列の途中の要素を削除することができませんでした。例えば、UniswapV2では作成されたPairのコントラクトアドレスがallPairsという配列に保存されます。
address[] public allPairs; https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Factory.sol#L11
この配列の中間にあるペアのアドレスを削除することはできず、0x00…で上書きして仮の削除を行うことは可能ですが、他の関数で配列の長さを利用するロジックを使用している場合、コードの整合性を保つのが難しくなります。JavaScriptの経験があると、この点での扱いにくさを感じることでしょう。
では、Stylusではこのような配列の扱いにくさがどのように改善されているのでしょうか?
Rustでは配列のある要素を削除するにはdelete()と呼ばれる関数を使って実現することができました。Stylusに同じような関数があるか探したのですが、StorageVecにはdelete()の機能は見つかりませんでした。
Stateとしてブロックチェーンに保存されるデータに関しては、Solidityと同様の制限がありそうです。
②-2. byte(string)の扱い方:Rustと同じように扱える
SolidityではStringの要素を切り取るsubstringを実行するためにはbyte一回変換して切り取る必要があり、直感的ではなく少し工夫が必要でした。
function substring(string memory str, uint startIndex, uint endIndex) public pure returns (string memory) { bytes memory strBytes = bytes(str); bytes memory result = new bytes(endIndex-startIndex); for(uint i = startIndex; i < endIndex; i++) { result[i-startIndex] = strBytes[i]; } return string(result); } https://github.com/0xywzx/SmartContractBook/blob/main/x.x.nft/contract/contracts/libraries/NFTSVG.sol#L95-L102
StylusでSubstringが実行できるか試すために下記のコードを作成しました。
#[view] pub fn substring(&self, text:String) ->Result<String, Vec<u8>> { ifletSome(substring) =text.get(2..3) { Ok(substring.to_string()) } else { Err(b"Index out of bounds".to_vec()) } }
実行結果は下記になり、当関数ではindexが2から3のものを抜き取るため “l” が取得できました。
Text before substring : Hello world Text after substring : l
ブロックチェーンに保存しない変数の場合は、Rustのコードを制約を受けずに利用することができるのかもしれません。
②に関しては全てのケースを調べきれていないため正確ではないかもしれませんが、ブロックチェーンに保存されるデータに関してはブロックチェーン特有の制約を受け、保存されない関数に関しては制約を受けずに実行できそうです。
③ SolidityとStylusの通信:Interfaceを定義するだけで簡単に実行できる
SolidityとStylusのコントラクトの通信は、interfaceを定義するだけで実行できます。これはSolidityのスマートコントラクトで他のスマートコントラクトを実行するコードとほぼ同じ仕様で、Solidity開発者であれば簡単に実装できます。
例えば、上記で利用したSolidityのコントラクトに定義されている“incrementSol”を実行する際には、下記のようにinterfaceを追加します。
sol_interface! { interfaceICounterSol { functionincrementSol() public; } } pub fn increment_sol_contract(&mut self, account: ICounterSol) -> Result<(), Vec<u8>> { Ok(account.increment_sol(self)?) }
“incrementSol”を呼び出す際は“increment_sol”と、キャメルケースからスネークケースに変更する必要があります。
実行すると下記のようになり、StylusのコントラクトからSolidityのコントラクトを実行することができました。
Before execute incrementSolContract : 1n Function is executed in this txhash : 0xdd6894b74abc68e40acc386ddfc32f503edac0a52429a0e3317d20c619a53b39 After execute incrementSolContract : 2n
まとめ
本記事ではStylusの概要やSolidityとStylusの比較について解説しました。
Rustを使ってスマートコントラクトを実装することはできるが、オンチェーンに保存されるデータの取り扱いに制限があったり、extenalやInterfaceなどスマートコントラクト独自の仕様があったりします。したがって、Rustを使えるからといってすぐにスマートコントラクト開発できるわけではなく、ある程度の学習コストはかかる印象を受けました。しかし、検証した結果から分かるようにガス代の削減などのStylusによるメリットは十分にあります。
また、L2エコシステム全体の話にはなりますが、Optimistic Rollupはセキュリティーやfinalityの速さの観点からZK Rollupに劣っており、Optimistic RollupはZK Rollupが流行るまでの繋ぎとよく言われています。しかし、Optimistic RollupがZK Rollupと比べると比較的実装が容易でEthereumに近い実装になっているからこそ、今回のチェーン自体を開発する以外のリソースを確保でき、今回のstylusのようなコア以外の開発を実現できてたと感じています。
これからStylusでとのようなアプリケーションが開発されるのか、StylusがArbitrumにどのような影響を与えるのか、今後の展開が非常に楽しみです。
本記事で使用したスマートコントラクトやスクリプトは、以下のGitHubリポジトリにて公開しています。
https://github.com/gaiax/BlockchainBiz/tree/main/20231030_arbitrum_stylus
参考リンク
- awesome-stylus
- Stylus Now Live — One Chain, Many Languages
- Quick start