リスク警告:「仮想通貨」「ブロックチェーン」の名のもとでの違法な資金調達のリスクに注意してください。—銀行保険監督管理委員会など5部門
検索
ログイン
简中
繁中
English
日本語
한국어
ภาษาไทย
Tiếng Việt
BTC
ETH
HTX
SOL
BNB
View Market
Solidity に加えて、他に注目に値する EVM 言語は何ですか?
Foresight News
特邀专栏作者
2023-03-18 09:30
この記事は約7632文字で、全文を読むには約11分かかります
EVM DSL設計の最新テクノロジーを学び、優れた言語を設計するにはどうすればよいですか?

オリジナル編集: 0x11、Foresight News

オリジナル編集: 0x11、Foresight News

Ethereum Virtual Machine (EVM) は、256 ビットのスタックベースのグローバルにアクセス可能なチューリング マシンです。アーキテクチャが他の仮想マシンや物理マシンとは大きく異なるため、EVM にはドメイン固有言語 DSL が必要です (注: ドメイン固有言語とは、特定のアプリケーション ドメインに焦点を当てたコンピューター言語を指します)。

この記事では、EVM DSL 設計の最先端を検証し、Solidity、Vyper、Fe、Huff、Yul、ETK の 6 つの言語を紹介します。

言語バージョン

  • Solidity: 0.8.19 

  • Vyper: 0.3.7 

  • Fe: 0.21.0 

  • Huff: 0.3.1 

  • ETK: 0.2.1 

  • Yul: 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

Solidity は、C、Java、JavaScript に似た構文を持つ高級言語です。これは TVL で最も人気のある言語であり、次に優れた言語の TVL の 10 倍です。コードを再利用するために、オブジェクト指向パターンが使用されます。このパターンでは、スマート コントラクトがクラス オブジェクトとして扱われ、多重継承が利用されます。コンパイラは C++ で書かれており、将来的には Rust に移行する予定です。

可変コントラクト フィールドは、その値がコンパイル時 (定数) またはデプロイメント時 (不変) で既知でない限り、永続ストレージに保存されます。コントラクトで宣言されたメソッドは、デフォルトで純粋、ビュー、支払い可能、または支払い不可能として宣言できますが、状態は変更できます。純粋なメソッドは実行環境から読み取りませんし、永続ストレージへの読み取りや書き込みもできません。つまり、純粋なメソッドは同じ入力が与えられた場合に常に同じ出力を返し、副作用は発生しません。ビュー メソッドは永続ストレージまたは実行環境からデータを読み取ることができますが、永続ストレージに書き込むことはできず、トランザクション ログの追加などの副作用を引き起こすこともできません。 Payable メソッドは、永続ストレージの読み取りと書き込み、実行環境からのデータの読み取り、副作用の生成、および呼び出しに添付された ETH の受け取りが可能です。非支払いメソッドは支払い可能なメソッドと同じですが、現在の実行コンテキストに ETH がアタッチされていないことを確認する実行時チェックがあります。

注: トランザクションに ETH を添付することは、ガス料金の支払いとは別です。添付された ETH はコントラクトによって受け取られ、コンテキストを復元することで承認または拒否できます。

コントラクトのスコープ内で宣言された場合、メソッドはプライベート、内部、パブリック、または外部の 4 つの可視性修飾子のいずれかを指定できます。プライベート メソッドには、現在のコントラクト内の「ジャンプ」命令を介して内部的にアクセスできます。継承されたコントラクトはプライベート メソッドに直接アクセスできません。内部メソッドには、「ジャンプ」命令を介して内部的にアクセスすることもできますが、継承されたコントラクトでは内部メソッドを直接使用できます。パブリック メソッドには、新しい実行コンテキストを作成する「call」命令を介して外部コントラクトからアクセスでき、メソッドを直接呼び出す場合は内部的にジャンプを介してアクセスできます。パブリック メソッドには、メソッド呼び出しの先頭に「this.」を付けることで、同じコントラクト内から新しい実行コンテキストでアクセスすることもできます。外部メソッドは、別のコントラクトからのものであっても、同じコントラクト内からのものであっても、「call」命令を通じてのみアクセスできます。メソッド呼び出しの前に「this.」を追加する必要があります。

注: 「ジャンプ」命令はプログラム カウンターを操作し、「コール」命令はターゲット コントラクトの実行中に新しい実行コンテキストを作成します。可能な場合は、「call」の代わりに「jump」を使用すると、ガス効率が高くなります。

Solidity では、ライブラリを定義する 3 つの方法も提供しています。 1 つ目は外部ライブラリです。これはチェーン上に個別にデプロイされ、コントラクトが呼び出されたときに動的にリンクされ、「delegatecall」コマンドを通じてアクセスされるステートレス コントラクトです。これは、外部ライブラリに対するツールのサポートが不十分であり、「デリゲート呼び出し」が高価であり、永続ストレージから追加のコードを読み込む必要があり、デプロイするには複数のトランザクションが必要であるため、最も一般的ではないアプローチです。内部ライブラリは、各メソッドを内部メソッドとして定義する必要があることを除いて、外部ライブラリと同じ方法で定義されます。コンパイル時に、内部ライブラリは最終コントラクトに埋め込まれ、デッド コード分析フェーズ中に、ライブラリ内の未使用のメソッドが削除されます。 3 番目の方法は内部ライブラリと似ていますが、データ構造と関数をライブラリ内で定義するのではなく、ファイル レベルで定義され、直接インポートして最終コントラクトで使用できます。 3 番目の方法では、人間とコンピューターの対話が向上し、カスタム データ構造を使用し、グローバル スコープで関数を適用し、一部の関数にエイリアス演算子をある程度適用できます。

コンパイラは 2 つの最適化パスを提供します。 1 つ目は、最終バイトコードに対して最適化操作を実行する命令レベルのオプティマイザーです。 2 つ目は、コンパイル プロセスでの中間表現 (IR) としての Yul 言語 (詳細は後述) の使用が最近増加しており、生成された Yul コードを最適化します。

コントラクト内のパブリックおよび外部メソッドと対話するために、Solidity はコントラクトと対話するためのアプリケーション バイナリ インターフェイス (ABI) 標準を指定します。現在、Solidity ABI は EVM DSL の事実上の標準とみなされています。外部インターフェイスを指定するイーサリアム ERC 標準は、Solidity の ABI 仕様およびスタイル ガイドに従って実装されます。他の言語も、多少の差異はあるものの、Solidity の ABI 仕様に従っています。

Solidity は、EVM 命令セットへの低レベルのアクセスを可能にするインライン Yul ブロッ​​クも提供します。 Yul ブロッ​​クには Yul 機能のサブセットが含まれています。詳細については、Yul セクションを参照してください。これは、ガスの最適化、高レベルの構文ではサポートされていない機能の利用、ストレージ、メモリ、および呼び出しデータのカスタマイズによく使用されます。

Solidity の人気により、開発者ツールは非常に成熟しており、よく設計されており、Foundry はこの点で著名な代表者です。

以下は、Solidity で書かれた簡単なコントラクトです。

Vyper

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

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

Huff は、手動スタック制御と EVM 命令セットの最小限の抽象化を備えたアセンブリ言語です。 「#include」ディレクティブを使用すると、インクルードされたハフ ファイルをコンパイル時に解析してコードを再利用できます。このコンパイラは元々、極度に最適化された楕円曲線アルゴリズム用に Aztec チームによって作成されましたが、後に TypeScript で書き直され、その後 Rust で書き直されました。

定数はコンパイル時に定義する必要があり、不変は現在サポートされておらず、永続ストレージ変数は言語で明示的に定義されていません。名前付きストレージ変数は高レベルの抽象化であるため、Huff での永続ストレージへの書き込みは、書き込みの場合はオペコード「sstore」、読み取りの場合は「sload」を介して行われます。カスタム ストレージ レイアウトはユーザー定義することも、慣例によりコンパイラの組み込み "FREE_STORAGE_POINTER" を使用してゼロから開始して各変数をインクリメントすることもできます。格納された変数を外部からアクセスできるようにするには、変数を読み取って呼び出し元に返すことができるコード パスを手動で定義する必要があります。

外部関数も高級言語によって導入された抽象化であるため、Huff には外部関数の概念がありません。ただし、ほとんどのプロジェクトは、程度は異なりますが、他の高級言語 (最も一般的なのは Solidity) の ABI 仕様に従います。一般的なパターンは、生の呼び出しデータをロードし、それを使用して関数セレクターとの一致をチェックする「スケジューラー」を定義することです。一致する場合、後続のコードが実行されます。スケジューラはユーザー定義であるため、異なるスケジューリング パターンに従う場合があります。 Solidity はスケジューラー内のセレクターを名前のアルファベット順にソートし、Vyper は数値的にソートして実行時に二分検索を実行します。また、ほとんどのハフ スケジューラーは予想される関数使用頻度によってソートしますが、ジャンプ テーブルはほとんど使用しません。現在、ジャンプ テーブルは EVM でネイティブにサポートされていないため、ジャンプ テーブルを実装するには「codecopy」などのイントロスペクション命令が必要です。

組み込み関数は、「#define fn」ディレクティブを使用して定義されます。これは、柔軟性のためにテンプレート パラメーターを受け入れ、関数の最初と最後で予想されるスタックの深さを指定できます。これらの関数は内部にあるため外部からアクセスすることはできず、内部アクセスにはジャンプ命令を使用する必要があります。

条件文やループ文などの他の制御フローは、ジャンプ ターゲットを使用して定義できます。ジャンプ ターゲットは、コロンが後に続く識別子によって定義されます。これらのターゲットへのジャンプは、識別子をスタックにプッシュし、ジャンプ命令を実行することで実行できます。これはコンパイル時にバイトコード オフセットに解決されます。

マクロは「#defineマクロ」で定義しますが、それ以外は内部関数と同じです。主な違いは、マクロがコンパイル時に「ジャンプ」命令を生成せず、マクロの本体をファイル内の各呼び出しに直接コピーすることです。

この設計では、何度も呼び出すとコード サイズが増加するという犠牲を払って、任意のジャンプの削減と実行時のガス コストとの関係を考慮します。 「MAIN」マクロはコントラクトのエントリ ポイントとみなされ、その本体の最初の命令がランタイム バイトコードの最初の命令になります。

コンパイラーに組み込まれているその他の機能には、ロギングのためのイベント ハッシュ生成、ディスパッチのための関数セレクター、エラー処理のためのエラー セレクター、組み込み関数およびマクロのためのコード サイズ チェッカーなどが含まれます。

注: 「// [count]」のようなスタック コメントは必須ではありません。これらは、行の実行終了時のスタックの状態を示すためにのみ使用されます。

ハフで書かれた簡単な契約は次のとおりです。

ETK

EVM ツールキット (ETK) は、手動スタック管理と最小限の抽象化を備えたアセンブリ言語です。コードは「%include」および「%import」ディレクティブを通じて再利用でき、コンパイラは Rust で書かれています。

Huff と ETK の注目すべき違いの 1 つは、Huff が initcode (コンストラクター コードとも呼ばれる) にわずかな抽象化を追加することです。この抽象化は、特別な「CONSTRUCTOR」マクロを定義することでオーバーライドできます。 ETK では、これらは抽象化されていないため、initcode とランタイム コードを一緒に定義する必要があります。

Huff と同様に、ETK は「sload」および「sstore」命令を通じて永続ストレージへの読み取りと書き込みを行います。ただし、定数または不変のキーワードはありませんが、ETK の 2 種類のマクロ (式マクロ) のいずれかを使用して定数をエミュレートできます。式マクロはディレクティブとして解析されませんが、他のディレクティブで使用できる数値を生成します。たとえば、「プッシュ」コマンドを正確に生成しない可能性がありますが、「プッシュ」コマンドに含める数値を生成する可能性があります。

前述したように、外部関数は高級言語の概念であるため、コード パスを外部に公開するには、関数セレクター ディスパッチャーを作成する必要があります。

組み込み関数は他の言語のように明示的に定義されていません。代わりに、ジャンプ ターゲットにユーザー定義のエイリアスを指定し、その名前によってジャンプすることができます。これにより、ループや条件文などの他の制御フローも可能になります。

ETK は 2 種類のマクロをサポートしています。 1 つ目は、任意の数の引数を受け入れ、他の命令で使用できる数値を返す式マクロです。式マクロは命令を生成しませんが、即値または定数を生成します。ただし、ディレクティブ マクロは任意の数の引数を受け入れ、コンパイル時に任意の数のディレクティブを生成します。 ETK の命令マクロはハフ マクロに似ています。

ETK で書かれた簡単なコントラクトを次に示します。

Yul

Yul は、高レベルの制御フローと多くの抽象化を備えたアセンブリ言語です。これは Solidity ツールチェーンの一部であり、オプションで Solidity ビルド パイプラインで使用できます。 Yul はスタンドアロン言語ではなくコンパイルターゲットであることを目的としているため、コードの再利用はサポートされていません。そのコンパイラは C++ で書かれており、Solidity パイプラインの残りの部分とともに Rust に移行する計画があります。

Yul では、コードはオブジェクトに分割され、オブジェクトにはコード、データ、およびネストされたオブジェクトを含めることができます。したがって、Yul には定数や外部関数はありません。コード パスを外部に公開するには、関数セレクター ディスパッチャーを定義する必要があります。

スタック命令と制御フロー命令を除いて、ほとんどの命令は Yul の関数として公開されます。ディレクティブをネストしてコード サイズを削減したり、一時変数に割り当てて他のディレクティブに渡して使用したりすることもできます。条件付き分岐では、値が 0 以外の場合に実行される「if」ブロックを使用できますが、「else」ブロックはないため、複数のコード パスを処理するには、任意の数のケースを処理するために「switch」を使用する必要があります。デフォルト」フォールバック オプション。ループは「for」ループを使用して実行できます。その構文は他の高級言語とは異なりますが、同じ基本機能を提供します。組み込み関数は、「function」キーワードを使用して定義でき、高級言語の関数定義に似ています。

Yul のほとんどの機能は、インライン アセンブリ ブロックを使用して Solidity で公開されます。これにより、開発者は抽象化を打ち破ったり、カスタム機能を記述したり、高レベル構文では利用できない機能で Yul を使用したりすることができます。ただし、この機能を使用するには、呼び出しデータ、メモリ、ストレージの観点から Solidity の動作を深く理解する必要があります。

ユニークな機能もいくつかあります。 「datasize」、「dataoffset」、および「datacopy」関数は、文字列エイリアスを介して Yul オブジェクトを操作します。 「setimmutable」および「loadimmutable」関数を使用すると、コンストラクター内で不変パラメーターを設定およびロードできますが、その使用は制限されています。 「memoryguard」関数は、特定の範囲のメモリのみが割り当てられることを意味し、コンパイラが追加の最適化のために保護された範囲を超えてメモリを使用できるようにします。最後に、「verbatim」により、Yul コンパイラーが認識しないディレクティブの使用が許可されます。

以下は Yul で書かれた簡単な契約書です。

優れた EVM DSL の特徴

優れた EVM DSL は、ここにリストされている各言語の長所と短所から学ぶ必要があり、条件付きステートメント、パターン マッチング、ループ、関数など、ほとんどすべての最新言語の基本もカバーしている必要があります。コードは曖昧さをなくし、コードの美しさや読みやすさを考慮して最小限の暗黙的な抽象化を追加する必要があります。一か八かの正確性が重要な環境では、コードのすべての行が明示的に説明可能である必要があります。また、優れた言語の中心には、明確に定義されたモジュール システムがなければなりません。どの項目がどのスコープで定義され、どの項目がアクセス可能であるかを明確に示す必要があります。モジュール内のすべての項目はデフォルトでプライベートである必要があり、外部からパブリックにアクセスできるのは明示的にパブリックな項目のみです。

EVM のようなリソースに制約のある環境では、効率が重要になります。効率は多くの場合、マクロを介したコンパイル時のコード実行、適切に設計された再利用可能なライブラリを作成する豊富な型システム、一般的なオンチェーン対話用のラッパーなどの低コストの抽象化を提供することで実現されます。マクロはコンパイル時にコードを生成します。これは、一般的な操作のボイラープレート コードを削減するのに最適です。また、ハフのような場合、コード サイズと実行時の効率をトレードオフするために使用できます。豊富な型システムにより、より表現力豊かなコード、実行前にエラーを検出するためのコンパイル時チェックの強化が可能になり、型チェックされたコンパイラ組み込み関数と組み合わせることで、インライン アセンブリの大部分が不要になる可能性があります。ジェネリックでは、Null 許容値 (外部コードなど) を「オプション」型でラップしたり、エラーが発生しやすい操作 (外部呼び出しなど) を「結果」型でラップしたりすることもできます。これら 2 つのタイプは、ライブラリ作成者が、失敗した結果を復元するコード パスまたはトランザクションを定義することで、開発者に各結果の処理を強制できる方法の例です。ただし、これらはコンパイル時の抽象化であり、実行時に単純な条件ジャンプに解決されることに注意してください。開発者にコンパイル時にすべての結果を処理させると、初期開発時間が長くなりますが、実行時に予期せぬ事態がはるかに少なくなるという利点があります。

開発者にとって柔軟性も重要であるため、複雑な操作のデフォルトは安全で効率性が低い可能性のあるルートである必要がありますが、より効率的なコード パスやサポートされていない機能を使用する必要がある場合もあります。このため、インライン アセンブリは開発者にオープンであり、ガードレールなしで行う必要があります。 Solidity のインライン アセンブリでは、簡素化とオプティマイザーの配信向上のためにいくつかのガードレールが設けられていますが、実行環境を完全に制御する必要がある場合は、開発者にこれらの権限を付与する必要があります。

結論は

結論は

元のリンク

元のリンク

ETH
スマートコントラクト
安全性
Odaily公式コミュニティへの参加を歓迎します
購読グループ
https://t.me/Odaily_News
チャットグループ
https://t.me/Odaily_CryptoPunk
公式アカウント
https://twitter.com/OdailyChina
チャットグループ
https://t.me/Odaily_CryptoPunk