はじめに
ArbitrumでSolidity以外の言語でコントラクトが作れるようになった。
今までもSolanaではRust、EVMでもVyperを使えばPythonで作れたけど、今度のこれはそれどころじゃない、WASMなので、
GoでもJavaでもなんでもござれ(JavaScriptみたいに独自のランタイム持っていると結構厳しいみたいやけど)。
とりあえずSolidityの代わりにRustが使えるようになったっぽいので、今回はこれでやってみる。
Rustでやるとメモリの節約(100倍から500倍)になり、ガス代も削減(10から70倍)されるらしい。
そして誰でも今後新たなプログラミング言語を追加していくことができるらしい。
あえてCOBOLで行ってみてはどうか、と昔秀丸でCOBOLを書いていたおっさんは思うのであった。
Stylusとは
はじめに座学を。
StylusマルチVMと呼ばれる技術。EVMに新たに仮想マシンが追加され、EthereumのSolidityコントラクトと全く同じように動くらしい。
もしかして、Ethereum完全互換を謳ってるTaikoって立場無くしちゃう感じ?まぁあっちはzkロールアップだけど。
新たな仮想マシンはWASMで動くらしく、そんなわけでWASMにコンパイルされる言語はなんでもコントラクトになるという理屈。
そしてWASMはEVMより高速で効率がいいらしい。
これでRustの開発者(300万人)やCの開発者(1200万人)がブロックチェーン開発に傾れ込んでくるぜ、って言う触れ込みだけど、そんなんSolidity覚えたらええだけの話で、傾れ込んでこない理由は他にもあるやろ常識的に考えて。
あと今までのEVMはすでに古いものというディスが公式ブログで始まってた。今まで動いてたモジュールをディスる文化は個人的にあまり好きではないノリ。
なお、Rustでコントラクトを作る一番のメリットはガス代よりもリエントランシー耐性がつくことだと思っている。
Rust SDKはデフォルトでリエントランシーが無効らしい、すごい。頭の中ぐるぐるしなくて済む。素敵。
触ってみた
準備
RustやVSCodeなどの事前にインストールしておく必要がある
また、Arbitrum SepoliaでもStylusは有効だが、今回はローカルにノードを立てる(要Docker)
プロジェクト作成
開発に必要なツールをcargoコマンドなどでインストールする
(環境構築)
その後、
shellcargo stylus new <YOUR_PROJECT_NAME>
とすればOK。
簡単なコントラクトが最初からテンプレートとして作成されている
rust//! //! Stylus Hello World //! //! The following contract implements the Counter example from Foundry. //! //! ``` //! contract Counter { //! uint256 public number; //! function setNumber(uint256 newNumber) public { //! number = newNumber; //! } //! function increment() public { //! number++; //! } //! } //! ``` //! //! The program is ABI-equivalent with Solidity, which means you can call it from both Solidity and Rust. //! To do this, run `cargo stylus export-abi`. //! //! Note: this code is a template-only and has not been audited. //! // Allow `cargo stylus export-abi` to generate a main function. #![cfg_attr(not(feature = "export-abi"), no_main)] extern crate alloc; /// Import items from the SDK. The prelude contains common traits and macros. use stylus_sdk::{alloy_primitives::U256, prelude::*}; // Define some persistent storage using the Solidity ABI. // `Counter` will be the entrypoint. sol_storage! { #[entrypoint] pub struct Counter { uint256 number; } } /// Declare that `Counter` is a contract with the following external methods. #[public] impl Counter { /// Gets the number from storage. pub fn number(&self) -> U256 { self.number.get() } /// Sets a number in storage to a user-specified value. pub fn set_number(&mut self, new_number: U256) { self.number.set(new_number); } /// Sets a number in storage to a user-specified value. pub fn mul_number(&mut self, new_number: U256) { self.number.set(new_number * self.number.get()); } /// Sets a number in storage to a user-specified value. pub fn add_number(&mut self, new_number: U256) { self.number.set(new_number + self.number.get()); } /// Increments `number` and updates its value in storage. pub fn increment(&mut self) { let number = self.number.get(); self.set_number(number + U256::from(1)); } }
「cargo stylus export-abi」とすれば他のコントラクトからcallするためのインターフェースも生成可能。やってないけどverifyもcargoコマンドからできるらしい。explorerにRustコードが反映されるんか?
デプロイ/実行
(デプロイ手順)
結果がこちら。
shellproject metadata hash computed on deployment: "1127f72e245f7aefced1b75129da2d50e25f1911a703748299031df47408ed50" stripped custom section from user wasm to remove any sensitive data contract size: 7.3 KB wasm data fee: 0.000065 ETH deployed code at address: 0x11b57fe348584f042e436c6bf7c3c3def171de49 deployment tx hash: 0x073042d1d3303dcf0a9f9478461764d39dfe34420e33ac1a3ba8c7dfc57f5ad9 contract activated and ready onchain with tx hash: 0xd3db95a5d20b003ac34efd36e16da365a99a3d8debd531d97370b44f258b1526
無事、ローカルのtestネットに「0x11b57fe348584f042e436c6bf7c3c3def171de49」としてデプロイされた。
そして実行してみた。
shellcast call --rpc-url 'http://localhost:8547' --private-key / 0xb6b15c8cb491557369f3c7d2c287b053eb229daa9c22138887752191c9520659 / 0x11b57fe348584f042e436c6bf7c3c3def171de49 "number()(uint256)" 0 // ここに結果が返ってくる
shellcast send --rpc-url 'http://localhost:8547' --private-key / 0xb6b15c8cb491557369f3c7d2c287b053eb229daa9c22138887752191c9520659 / 0x11b57fe348584f042e436c6bf7c3c3def171de49 "increment()" blockHash 0x5dffbf09b198ce52da39f242a42a20a660bdfde67b1e9375a17e65be9e5e8cf7 blockNumber 21 contractAddress cumulativeGasUsed 97388 effectiveGasPrice 100000000 from 0x3f1Eae7D46d88F08fc2F8ed27FCb2AB183EB2d0E gasUsed 97388 logs [] logsBloom 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 root status 1 (success) transactionHash 0xc5f5fb9e0aced5a903c8e35c7e3fac8770d906e5b0c44306916471f011225d6b transactionIndex 1 type 2 blobGasPrice blobGasUsed authorizationList to 0x11B57FE348584f042E436c6Bf7c3c3deF171de49 gasUsedForL1 "0x0" l1BlockNumber "0x1875"
shellcast call --rpc-url 'http://localhost:8547' --private-key / 0xb6b15c8cb491557369f3c7d2c287b053eb229daa9c22138887752191c9520659 / 0x11b57fe348584f042e436c6bf7c3c3def171de49 "number()(uint256)" 1 // ちゃんとインクリメントされている
所感
OpenZeppelinのRust版とかが出てくるまでは、みんなERC20独自実装になるのかな、COMSAを思い出す。
それまでは品質の担保はお預けかな。でもまぁCairo用のOpenZeppelinもぱっと作ってたし、いずれRust用のやつも誰かが作るやろ、多分。
結局はバイトコードなので、ethers.jsやFoundry、Hardhatから操作可能なのは嬉しい。
ガス代の削減という明確なメリットが出てきた以上、(Arbitrumのみで展開するなら)Solidityでコントラクトを作成するメリットは薄い。
現在、c/c++/Bf/Zigのライブラリがあるっぽいので、興味のある方はぜひ。
やろうと思えばGrass(vとWとwだけで構成された難解言語)やなでしこ(日本語でプログラミングできるやつ)でも可能なんやろな。
参考リンク
Stylus Gentle Introduction
Arbitrum Docs
https://docs.arbitrum.io/stylus/stylus-gentle-introduction