Solidity 以外に注目に値する EVM 言語は何ですか?
作者: jtriley.ethjtriley.eth
編集者: 0x11、Foresight News
Ethereum Virtual Machine (EVM) は、256 ビットのスタックベースのグローバルにアクセス可能なチューリング マシンです。 EVM は他の仮想マシンや物理マシンとはアーキテクチャが大きく異なるため、ドメイン固有言語 DSL が必要です (注: ドメイン固有言語とは、特定のアプリケーション ドメインに焦点を当てたコンピューター言語を指します)。
この記事では、Solidity、Vyper、Fe、Huff、Yul、ETK の 6 つの言語をカバーする EVM DSL 設計の最先端について見ていきます。
言語バージョン
堅牢性: 0.8.19
ヴァイパー: 0.3.7
鉄: 0.21.0
ハフ: 0.3.1
ETK: 0.2.1
ユル: 0.8.19
この記事を読むには、EVM、スタック、プログラミングの基本を理解している必要があります。
イーサリアム仮想マシンの概要
EVM は 256 ビットのスタックベースのチューリング マシンです。ただし、コンパイラについて詳しく説明する前に、いくつかの機能を紹介する必要があります。
EVM は「チューリング完了」しているため、「停止問題」が発生します。つまり、プログラムを実行する前には、それが将来終了するかどうかを判断する方法はありません。 EVM がこの問題を解決する方法は、「ガス」を通じて計算単位を測定することです。これは通常、命令の実行に必要な物理リソースに比例します。トランザクションごとのGasの量は制限されており、トランザクションの開始者はトランザクションで消費されたGasに比例したETHを支払わなければなりません。この戦略の影響の 1 つは、機能的に同一のスマート コントラクトが 2 つある場合、ガス消費量が少ない方がより多く採用されることです。その結果、プロトコルは極度のガス効率を競うことになり、エンジニアは特定のタスクでのガス消費を最小限に抑えるよう努めます。
さらに、コントラクトが呼び出されると、実行コンテキストが作成されます。このコンテキストでは、コントラクトには操作と処理のためのスタック、読み取りと書き込みのためのリニア メモリ インスタンス、コントラクトの読み取りと書き込みのためのローカル永続ストレージがあり、呼び出し「calldata」に添付されたデータは読み取ることはできますが、記録することはできません。 。
メモリに関する重要な注意点は、そのサイズに明確な「上限」はありませんが、それでも制限があるということです。メモリ拡張のガス コストは動的です。しきい値に達すると、メモリ拡張のコストは二次関数的に増加します。これは、ガス コストが追加メモリ割り当ての 2 乗に比例することを意味します。
コントラクトは、さまざまな命令を使用して他のコントラクトを呼び出すこともできます。 「call」命令は、データとオプションの ETH をターゲット コントラクトに送信し、ターゲット コントラクトの実行が停止するまで独自の実行コンテキストを作成します。 「staticcall」ディレクティブは「call」と同じですが、静的呼び出しが完了する前にグローバル状態のどの部分も更新されていないことをアサートするチェックを追加します。最後に、「delegatecall」ディレクティブは、前のコンテキストからの環境情報を保持することを除いて、「call」と同様に動作します。これは通常、外部ライブラリとプロキシ コントラクトに使用されます。
なぜ言語設計が重要なのか
ドメイン固有言語 (DSL) は、非定型アーキテクチャを操作する場合に必要です。 LLVM などのコンパイラ ツールチェーンは存在しますが、プログラムの正確性と計算効率が重要な状況では、それらに依存してスマート コントラクトを処理することは理想的とは言えません。
スマート コントラクトはデフォルトでは不変であり、ブロックチェーン仮想マシン (VM) の特性を考慮すると、金融アプリケーションでは一般的な選択肢であるため、プログラムの正確性が重要です。 EVM にはアップグレード可能な解決策がありますが、それはせいぜいパッチであり、最悪の場合は任意のコードが実行される脆弱性です。
計算を最小限に抑えると経済的なメリットが得られますが、安全性は犠牲にならないため、計算効率も重要です。
つまり、EVM DSL は、柔軟性をあまり犠牲にすることなく、さまざまなトレードオフを行うことで、プログラムの正確性とガス効率のバランスをとり、どちらか一方を達成する必要があります。
言語の概要
各言語について、その顕著な機能と設計の選択肢について説明し、単純なカウント機能のスマート コントラクトを含めます。言葉の人気度は、Defi Llama の Total Value Locked (TVL) データに基づいて決定されます。
堅実性
Solidity は、構文が C、Java、JavaScript に似た高水準言語です。これは TVL で最も人気のある言語であり、TVL は次に人気のある言語の 10 倍です。コードを再利用するために、オブジェクト指向パターンが使用されます。このパターンでは、スマート コントラクトがクラス オブジェクトとして扱われ、多重継承が活用されます。コンパイラは C++ で書かれており、将来的には Rust に移行する予定です。
可変コントラクト フィールドは、その値がコンパイル時 (定数) またはデプロイメント時 (不変) で既知でない限り、永続ストレージに保存されます。コントラクト内で宣言されたメソッドは、デフォルトで純粋、ビュー、支払可能、または支払不能として宣言できますが、ステータスは変更可能です。純粋なメソッドは実行環境からデータを読み取らず、永続ストレージに読み書きすることもできません。つまり、同じ入力が与えられた場合、純粋なメソッドは常に同じ出力を返し、副作用はありません。ビュー メソッドは永続ストアまたは実行環境からデータを読み取ることができますが、永続ストアに書き込んだり、トランザクション ログの追加などの副作用を作成したりすることはできません。 Payable メソッドは、永続ストレージの読み取りと書き込み、実行環境からのデータの読み取り、副作用の生成、および呼び出しに添付された ETH の受信を行うことができます。非支払いメソッドは支払い可能なメソッドと同じですが、現在の実行コンテキストに ETH が接続されていないことをアサートする実行時チェックがあります。
注: トランザクションに ETH を添付することは、ガス料金の支払いとは別のものです。添付された ETH はコントラクトによって受け取られ、コンテキストを復元することで受け入れるか拒否するかを選択できます。
コントラクトのスコープ内で宣言された場合、メソッドはプライベート、内部、パブリック、または外部の 4 つの可視性修飾子を指定できます。プライベート メソッドには、現在のコントラクト内の「ジャンプ」命令を介して内部的にアクセスできます。継承されたコントラクトはプライベート メソッドに直接アクセスできません。内部メソッドには、「ジャンプ」命令を介して内部的にアクセスすることもできますが、継承されたコントラクトは内部メソッドを直接使用できます。パブリック メソッドには、新しい実行コンテキストを作成する「call」命令を介して外部コントラクトからアクセスでき、メソッドを直接呼び出す場合は内部的にジャンプを介してアクセスできます。メソッド呼び出しの先頭に「this.」を追加することで、新しい実行コンテキスト内の同じコントラクトからパブリック メソッドにアクセスすることもできます。外部メソッドは、異なるコントラクトからのものであっても、同じコントラクト内からのものであっても、「call」命令を通じてのみアクセスできます。「this.」はメソッド呼び出しの前に追加する必要があります。
注: 「jump」命令はプログラム カウンタを操作し、「call」命令はターゲット コントラクトの実行中に新しい実行コンテキストを作成します。可能であれば、「コール」の代わりに「ジャンプ」を使用してガスを節約してください。
Solidity では、ライブラリを定義する 3 つの方法も提供しています。 1 つ目は外部ライブラリです。これはチェーン上に個別にデプロイされ、コントラクトが呼び出されるときに動的にリンクされ、「delegatecall」命令を通じてアクセスされるステートレス コントラクトです。これは、外部ライブラリに対するツールのサポートが不十分であり、「delegatecall」が高価であり、永続ストレージから追加のコードを読み込む必要があり、デプロイするには複数のトランザクションが必要であるため、最も一般的ではないアプローチです。内部ライブラリは、すべてのメソッドを内部メソッドとして定義する必要があることを除いて、外部ライブラリと同じ方法で定義されます。コンパイル時に、内部ライブラリは最終コントラクトに埋め込まれ、デッド コード分析フェーズ中に、ライブラリ内の未使用のメソッドが削除されます。 3 番目の方法は内部ライブラリに似ていますが、データ構造と関数をライブラリ内で定義するのではなく、ファイル レベルで定義され、直接インポートして最終コントラクトで使用できます。 3 番目のアプローチは、カスタム データ構造を使用し、関数をグローバル スコープに適用し、エイリアス演算子を特定の関数に限定的に適用することにより、人間とコンピューターの対話性を向上させます。
コンパイラは 2 つの最適化パスを提供します。 1 つ目は命令レベルのオプティマイザーで、最終バイトコードに対して最適化操作を実行します。 2 つ目は、コンパイル プロセス中に中間表現 (IR) として Yul 言語 (これについては後で詳しく説明します) を使用し、生成された Yul コードに対して最適化操作を実行する機能が最近追加されたことです。
コントラクト内のパブリックおよび外部メソッドと対話するために、Solidity はコントラクトと対話するためのアプリケーション バイナリ インターフェイス (ABI) 標準を指定します。現在、Solidity ABI は EVM DSL の事実上の標準とみなされています。外部インターフェイスを指定するイーサリアム ERC 標準は、Solidity の ABI 仕様およびスタイル ガイドに従って実装されます。他の言語も、ほとんど逸脱せずに Solidity の ABI 仕様に従っています。
Solidity はインライン Yul ブロックも提供し、EVM 命令セットへの低レベルのアクセスを可能にします。 Yul ブロックには Yul 機能のサブセットが含まれています。詳細については、Yul セクションを参照してください。これは通常、ガスの最適化、上位レベルの構文ではサポートされていない機能の利用、およびストレージ、メモリ、および呼び出しデータのカスタマイズに使用されます。
Solidity の人気により、開発者ツールは非常に成熟しており、よく設計されており、Foundry はこの点で際立っています。
以下は、Solidity で書かれた簡単なコントラクトです。

ヴァイパー
Vyper は、Python に似た構文を持つ高級言語です。これは Python のほぼサブセットですが、いくつかの小さな違いがあります。これは 2 番目に人気のある EVM DSL です。 Vyper は、セキュリティ、可読性、監査可能性、ガス効率に関して最適化されています。オブジェクト指向パターンやインラインアセンブリは使用せず、コードの再利用もサポートしていません。そのコンパイラは Python で書かれています。
永続ストレージに格納される変数は、ファイル レベルで宣言されます。値がコンパイル時にわかっている場合は「定数」として宣言でき、値がデプロイメント時にわかっている場合は「不変」として宣言できます。パブリックの場合は、最終的なコントラクトで公開されます。変数の読み取り専用関数。定数と不変式の値は名前を通じて内部的にアクセスされますが、永続ストレージ内の可変変数には名前の先頭に「self」を追加することでアクセスできます。これは、格納された変数、関数パラメーター、およびローカル変数間の名前空間の競合を防ぐのに役立ちます。
Solidity と同様に、Vyper も関数属性を使用して関数の可視性と可変性を表します。 「@external」とマークされた関数は、「call」命令を通じて外部コントラクトからアクセスできます。 「@internal」とマークされた関数は、同じコントラクト内でのみアクセスでき、接頭辞として「self」を付ける必要があります。 「@pure」とマークされた関数は、実行環境または永続ストレージからデータを読み取ったり、永続ストレージに書き込んだり、副作用を作成したりすることはできません。 「@view」とマークされた関数は、実行環境または永続ストレージからデータを読み取ることはできますが、永続ストレージに書き込んだり、副作用を引き起こしたりすることはできません。 「@payable」とマークされた関数は、永続ストレージへの読み取りまたは書き込み、副作用の作成、ETH の受け取りが可能です。この可変属性を宣言していない関数は、デフォルトで支払い不可になります。つまり、支払い可能な関数と同じですが、ETH を受け取ることはできません。
Vyper コンパイラは、ローカル変数をスタックではなくメモリに保存することも選択します。これにより、コントラクトがよりシンプルかつ効率的になり、他の高級言語でよくある「スタックが深すぎる」問題が解決されます。ただし、これにはいくつかのトレードオフも伴います。
さらに、メモリ レイアウトはコンパイル時に把握しておく必要があるため、動的型の最大容量もコンパイル時に把握しておく必要があり、これが制限となります。さらに、EVM の概要セクションで説明したように、大量のメモリを割り当てると、非線形のガス消費が発生する可能性があります。ただし、多くのユースケースでは、このガスコストは無視できます。
Vyper はインライン アセンブリをサポートしていませんが、Solidity と Yul のほぼすべての機能が Vyper でも利用できるようにするために、より多くの組み込み関数が提供されています。低レベルのビット操作、外部呼び出し、およびプロキシ コントラクト操作には、組み込み関数を通じてアクセスでき、コンパイル時にオーバーレイ ファイルを提供することでカスタム ストレージ レイアウトを実装できます。
Vyper には豊富な開発ツール スイートはありませんが、より緊密に統合され、Solidity 開発ツールにプラグインできるツールがあります。注目すべき Vyper ツールには、実験と開発のための EVM および Vyper に関連する多くの組み込みツールを備えた Titanaboa インタープリタと、コンパイル時にコードを実行する Vyper ベースの Lisp である Dasy が含まれます。
以下は Vyper で書かれた簡単なコントラクトです。

鉄
Fe は Rust と同様の高水準言語で、現在開発が活発に行われていますが、ほとんどの機能はまだ利用できません。そのコンパイラは主に Rust で書かれていますが、中間表現 (IR) として Yul を使用し、C++ で書かれた Yul オプティマイザに依存しています。これは、Rust のネイティブ バックエンドである Sonatina の追加によって変わると予想されます。 Fe はコード共有にモジュールを使用するため、オブジェクト指向パターンは使用しませんが、変数、型、関数がモジュール内で宣言され、Rust と同様の方法でインポートできるモジュールベースのシステムを通じてコードを再利用します。
永続ストレージ変数はコントラクト レベルで宣言され、手動で定義されたゲッター関数なしではパブリックにアクセスできません。定数はファイルまたはモジュールレベルで宣言でき、コントラクト内でアクセスできます。不変のデプロイメント時変数は現在サポートされていません。
メソッドはモジュール レベルまたはコントラクト内で宣言でき、デフォルトでは純粋でプライベートです。コントラクト メソッドをパブリックにするには、定義の前に「pub」キーワードを追加する必要があります。これにより、外部からアクセスできるようになります。永続ストレージ変数から読み取るには、メソッドの最初のパラメータを「self」にする必要があります。変数名の前に「self.」を追加して、メソッドにローカル ストレージ変数への読み取り専用アクセスを与えます。永続ストレージの読み取りおよび書き込みを行うには、最初のパラメータは「mut self」である必要があります。 「mut」キーワードは、メソッドの実行中にコントラクトのストレージが変更可能であることを示します。環境変数へのアクセスは、通常「ctx」という名前のメソッドに「Context」パラメータを渡すことによって実行されます。
関数とカスタム型はモジュール レベルで宣言できます。デフォルトでは、モジュール項目はプライベートであり、「pub」キーワードが使用されない限りアクセスできません。ただし、契約レベルの「pub」キーワードと混同しないでください。モジュールのパブリック メンバーには、最終コントラクトまたは他のモジュール内でのみアクセスできます。
Fe は現在インライン アセンブリをサポートしていません。代わりに、命令はコンパイラ組み込み関数またはコンパイル時に命令に解決される特別な関数によってラップされます。
Fe は Rust の構文と型システムに従い、型エイリアス、サブタイプを持つ列挙型、トレイト、ジェネリックをサポートします。これに対するサポートは現在制限されていますが、現在取り組んでいます。特性はさまざまなタイプに対して定義および実装できますが、ジェネリックおよび特性制約はサポートされていません。列挙型はサブタイプをサポートし、それらにメソッドを実装できますが、外部関数でエンコードすることはできません。 Fe の型システムはまだ開発中ですが、開発者にとって、より安全でコンパイル時にチェックされるコードを作成できる可能性が大いにあります。
以下は Fe で書かれた簡単なコントラクトです。

ハフ
Huff は、手動スタック制御と EVM 命令セットの最小限の抽象化を備えたアセンブリ言語です。 「#include」ディレクティブを使用すると、インクルードされたハフ ファイルをコンパイル中に解析してコードを再利用できます。このコンパイラは元々、極度に最適化された楕円曲線アルゴリズム用に Aztec チームによって作成されましたが、後に TypeScript で書き直され、その後 Rust で書き直されました。
定数はコンパイル時に定義する必要があり、不変は現在サポートされておらず、永続ストレージ変数は言語で明示的に定義されていません。名前付きストレージ変数は高レベルの抽象化であるため、Huff での永続ストレージへの書き込みは、書き込みの場合はオペコード「sstore」、読み取りの場合は「sload」を介して行われます。カスタム ストレージ レイアウトはユーザーが定義することも、コンパイラの組み込み "FREE_STORAGE_POINTER" を使用して最初から開始して各変数をインクリメントする規則に従うこともできます。格納された変数を外部からアクセスできるようにするには、変数を読み取って呼び出し元に返すことができるコード パスを手動で定義する必要があります。
外部関数も高級言語によって導入された抽象化であるため、Huff には外部関数の概念がありません。ただし、ほとんどのプロジェクトは、程度は異なりますが、他の高級言語 (最も一般的には Solidity) の ABI 仕様に従います。一般的なパターンは、生の呼び出しデータをロードし、それを使用して一致する関数セレクターをチェックする「ディスパッチャー」を定義することです。一致する場合、後続のコードが実行されます。スケジューラはユーザー定義であるため、異なるスケジューリング パターンに従う場合があります。 Solidity はスケジューラー内のセレクターを名前のアルファベット順にソートし、Vyper は数値的にソートして実行時に二分検索を実行します。また、ほとんどの Huff スケジューラーは予想される関数の使用頻度によってソートしますが、ジャンプ テーブルはほとんど使用しません。現在、ジャンプ テーブルは EVM でネイティブにサポートされていないため、ジャンプ テーブルを実装するには「codecopy」などのイントロスペクション命令を使用する必要があります。
内部関数は#definefn」ディレクティブを使用して定義されます。これにより、柔軟性を高めるためにテンプレート パラメーターを受け入れ、関数の最初と最後で予想されるスタックの深さを指定できます。これらの関数は内部にあるため、外部からアクセスすることはできません。内部アクセスには「ジャンプ」命令を使用する必要があります。
条件ステートメントやループ ステートメントなどの他の制御フローでは、ジャンプ ターゲット定義を使用できます。ジャンプ ターゲットは、コロンが後に続く識別子によって定義されます。これらのターゲットへのジャンプは、識別子をスタックにプッシュし、ジャンプ命令を実行することで実行できます。これはコンパイル時にバイトコード オフセットに解決されます。
マクロは「#defineマクロ」で定義されており、それ以外は内部関数と同じです。主な違いは、マクロがコンパイル時に「ジャンプ」命令を生成せず、マクロの本体をファイル内の各呼び出しに直接コピーすることです。
この設計では、複数回呼び出された場合のコード サイズの増加と引き換えに、ランタイム ガス コストに対する任意のジャンプの削減がトレードオフになります。 「MAIN」マクロはコントラクトへのエントリ ポイントとみなされ、その本体の最初の命令がランタイム バイトコードの最初の命令になります。
コンパイラに組み込まれているその他の機能には、ロギングのためのイベント ハッシュ生成、スケジューリングのための関数セレクター、エラー処理のためのエラー セレクター、内部関数とマクロのためのコード サイズ チェッカーなどがあります。
注: 「//[count]」などのスタック コメントは必須ではありません。これらは、行の実行終了時のスタック状態を示すためにのみ使用されます。
ハフで書かれた簡単な契約は次のとおりです。

ETK
EVM ツールキット (ETK) は、手動スタック管理と最小限の抽象化を備えたアセンブリ言語です。コードは「%include」および「%import」ディレクティブを通じて再利用でき、コンパイラは Rust で書かれています。
Huff と ETK の大きな違いの 1 つは、Huff が initcode (コンストラクター コードとも呼ばれる) にわずかな抽象化を追加することです。この抽象化は、特別な「CONSTRUCTOR」マクロを定義することでオーバーライドできます。 ETK では、これらは抽象化されていないため、initcode とランタイム コードを一緒に定義する必要があります。
Huff と同様に、ETK は「sload」および「sstore」命令を通じて永続ストレージの読み取りと書き込みを行います。ただし、定数または不変のキーワードはありませんが、ETK の 2 つのマクロの 1 つである式マクロを使用して定数をシミュレートできます。式マクロは命令に解決されませんが、代わりに他の命令で使用できる数値を生成します。たとえば、「push」コマンドを完全に生成することはできませんが、「push」コマンドに含める数値を生成する場合があります。
前述したように、外部関数は高級言語の概念であるため、コード パスを外部に公開するには、関数セレクター スケジューラの作成が必要です。
他の言語のように内部関数は明示的に定義されていません。代わりに、ユーザー定義のエイリアスをジャンプ ターゲットに指定し、その名前によってジャンプすることができます。これにより、ループや条件文などの他の制御フローも可能になります。
ETK は 2 種類のマクロをサポートします。 1 つ目は、任意の数の引数を受け入れ、他の命令で使用できる数値を返す式マクロです。式マクロは命令を生成しませんが、代わりに即値または定数を生成します。ただし、ディレクティブ マクロは任意の数の引数を受け入れ、コンパイル時に任意の数のディレクティブを生成します。 ETK の命令マクロはハフ マクロに似ています。
以下は ETK で書かれた簡単なコントラクトです。

ユル
Yul は、高レベルの制御フローと多数の抽象化を備えたアセンブリ言語です。これは Solidity ツールチェーンの一部であり、オプションで Solidity ビルド パスで使用できます。 Yul はスタンドアロン言語ではなくコンパイルのターゲットとなることを目的としているため、コードの再利用はサポートされていません。そのコンパイラは C++ で書かれており、Solidity チャネルの残りの部分とともに Rust に移行する計画があります。
Yul では、コードはオブジェクトに分割され、オブジェクトにはコード、データ、およびネストされたオブジェクトを含めることができます。したがって、Yul には定数や外部関数はありません。コード パスを外部に公開するには、関数セレクター ディスパッチャーを定義する必要があります。
スタック命令と制御フロー命令を除いて、ほとんどの命令は Yul の関数として公開されます。命令をネストしてコード長を短縮したり、一時変数に割り当ててから他の命令に渡して使用したりできます。条件分岐では、値がゼロ以外の場合に実行される「if」ブロックを使用できますが、「else」ブロックがないため、複数のコードパスを処理するには、任意の数のケースを処理するために「スイッチ」を使用する必要があります。 「デフォルト」のフォールバック オプション。ループは「for」ループを使用して実行できます。その構文は他の高級言語とは異なりますが、同じ基本機能を提供します。内部関数は「function」キーワードを使用して定義でき、高級言語の関数定義に似ています。
Yul のほとんどの機能は、インライン アセンブリ ブロックを使用して Solidity で公開されます。これにより、開発者は抽象化を破ってカスタム機能を記述したり、高レベル構文では利用できない機能で Yul を使用したりすることができます。ただし、この機能を使用するには、呼び出しデータ、メモリ、ストレージに関する Solidity の動作を深く理解する必要があります。
ユニークな機能もいくつかあります。 「datasize」、「dataoffset」、および「datacopy」関数は、文字列エイリアスを通じて Yul オブジェクトを操作します。 「setimmutable」関数と「loadimmutable」関数を使用すると、コンストラクターで不変パラメータを設定およびロードできますが、その使用は制限されています。 「memoryguard」関数は、特定のメモリ範囲のみが割り当てられていることを示し、コンパイラが追加の最適化のために保護された範囲を超えてメモリを使用できるようにします。最後に、「verbatim」により、Yul コンパイラにとって未知の命令の使用が許可されます。
以下は Yul で書かれた簡単な契約書です。

優れた EVM DSL の特徴
優れた EVM DSL は、ここにリストされている各言語の長所と短所から学ぶ必要があり、条件分岐、パターン マッチング、ループ、関数など、ほとんどすべての最新言語に見られる基本も含まれている必要があります。コードは明示的である必要があり、コードの美しさや読みやすさのために追加される暗黙的な抽象化は最小限に抑えられます。一か八かの正確性が重要な環境では、コードのすべての行が明確に解釈可能である必要があります。さらに、明確に定義されたモジュール システムは、優れた言語の中核となるべきです。どの項目がどのスコープで定義され、どの項目がアクセス可能であるかを明確に示す必要があります。モジュール内のすべての項目はデフォルトでプライベートである必要があり、明示的にパブリックな項目のみが外部からパブリックにアクセスできるようにする必要があります。
EVM のようなリソースに制約のある環境では、効率が重要です。効率は多くの場合、マクロによるコンパイル時のコード実行、適切に設計された再利用可能なライブラリを作成するための豊富な型システム、一般的なオンチェーン相互作用のラッパーなどの低コストの抽象化を提供することで実現されます。マクロはコンパイル時にコードを生成します。これは、一般的な操作のボイラープレート コードを減らすのに役立ちます。また、ハフのような場合、コード サイズと実行時の効率をトレードオフするために使用できます。豊富な型システムにより、より表現力豊かなコードが可能になり、実行前にエラーを検出するためのコンパイル時チェックが強化され、型チェックされたコンパイラ組み込み関数と組み合わせることで、インライン アセンブリの大部分が不要になる可能性があります。ジェネリックでは、Null 許容値 (外部コードなど) を「オプション」型でラップしたり、エラーが発生しやすい操作 (外部呼び出しなど) を「結果」型でラップしたりすることもできます。これら 2 つのタイプは、ライブラリ作成者が、失敗した結果を回復するコード パスまたはトランザクションを定義することによって、開発者に各結果の処理を強制する方法の例です。ただし、これらはコンパイル時の抽象化であり、実行時に単純な条件ジャンプに解決されることに注意してください。開発者にコンパイル時にすべての結果を処理させると、初期開発時間が長くなりますが、実行時に予期せぬ事態がはるかに少なくなるという利点があります。
開発者にとって柔軟性も重要であるため、複雑な操作のデフォルトは安全で効率が低い可能性のあるルートである必要がありますが、場合によっては、より効率的なコード パスやサポートされていない機能を使用する必要があります。これを行うには、インライン アセンブリをガードレールなしで開発者に公開する必要があります。 Solidity のインライン アセンブリは、簡素化と最適化パスの向上のためにいくつかのガードレールを設定していますが、開発者が実行環境を完全に制御する必要がある場合は、これらの権限を付与する必要があります。
潜在的に役立つ機能には、コンパイル時に関数やその他の項目のプロパティを操作する機能などがあります。たとえば、「inline」属性を使用すると、効率を高めるためにさらにジャンプを作成するのではなく、単純な関数の本体を各呼び出しにコピーできます。 「abi」属性を使用すると、特定の外部関数によって生成された ABI を手動でオーバーライドして、さまざまなコーディング スタイルの言語に適応できます。さらに、オプションの関数スケジューラを定義できるため、より一般的に使用されると予想されるコード パスをさらに最適化するために高級言語内でカスタマイズできます。たとえば、「name」を実行する前に、セレクターが「transfer」または「transferFrom」であるかどうかを確認します。
結論は
EVM DSL 設計には長い道のりがあります。各言語には独自の設計上の決定があり、それらが将来どのように発展するかを見るのが楽しみです。開発者として、できるだけ多くの言語を学ぶことが最大の利益です。まず、複数の言語を学習し、それらの違いと類似点を理解することで、プログラミングとその基礎となるマシン アーキテクチャについての理解が深まります。第二に、言語には深いネットワーク効果と強い記憶力があります。 C#、Swift、Kotlin から Solidity、Sway、Cairo に至るまで、大手企業が独自のプログラミング言語を構築していることは疑いの余地がありません。これらの言語をシームレスに切り替える方法を学ぶことで、ソフトウェア エンジニアリングのキャリアに比類のない柔軟性がもたらされます。最後に、すべての言語の背後には多くの作業があることを理解することが重要です。完璧な人間はいませんが、無数の才能ある人々が、私たちのような開発者のために安全で楽しいエクスペリエンスを作成するために多大な努力を払っています。
