Solidity のコントラクトは、オブジェクト指向言語のクラスに似ています。状態変数の永続データと、これらの変数を変更できる関数が含まれています。別のコントラクト (インスタンス) で関数を呼び出すと、EVM 関数呼び出しが実行され、コンテキストが切り替わります。呼び出し元のコントラクトの状態変数にアクセスできないようにする. コントラクトとその関数は、何かが起こるために呼び出される必要がある. イーサリアムには、特定のイベントで関数を自動的に呼び出すための「cron」の概念はありません. Solidity では、コントラクトは次と似ていますオブジェクト指向言語におけるクラスの概念。コントラクトには、データの保存に使用される状態変数と、これらの変数の変更に使用される関数が含まれています。異なるコントラクト インスタンスが関数を呼び出すと、EVM 関数呼び出しが実行されます。このようにして、コンテキストが切り替えられた後、コントラクトを呼び出すプロセスで状態変数は相互にアクセスできなくなります。コントラクトとコントラクトの関数には、走るよう呼び出される。イーサリアムには、スケジュールされたタスクを自動的に呼び出す方法である cron の概念があります。
可視性とゲッター
状態変数の可視性
公共
内部
プライベート
関数の可視性
外部の
外部関数はコントラクト インターフェイスの一部です。つまり、外部関数は他のコントラクトやトランザクション経由で呼び出すことができます。外部関数を f
内部で呼び出すことはできません (つまり、 f()
機能しませんが、 this.f()
機能します)。
公共
パブリック関数はコントラクト インターフェイスの一部であり、内部的に呼び出すことも、メッセージ呼び出しを介して呼び出すこともできます。
内部
プライベート
関数修飾子
修飾子を使用すると、宣言的な方法で関数の動作を変更できます。たとえば、修飾子を使用して、関数を実行する前に条件を自動的にチェックできます。
定数および不変の状態変数
constant
状態変数はまたは として宣言できます immutable
。どちらの場合も、コントラクトの構築後に変数を変更することはできません。変数の場合 constant
、値はコンパイル時に固定する必要がありますが、 の場合は immutable
構築時に割り当てることができます。
定数をコンパイルすると、変数の値は固定化され、変更できなくなります。Immutable はコントラクトの作成後に固定化され、コントラクトのコンストラクター内で引き続き変更できます。
機能
関数は宣言できます view
が、その場合、関数は状態を変更しないことを約束します。
関数を宣言することもできます pure
が、その場合、関数は状態の読み取りや変更を行わないことを約束します。
フォールバック機能
コントラクトには 、 または (両方ともキーワードなし )fallback
のいずれかを使用して宣言された関数を 1 つだけ含めることができます 。この関数には可視性が必要です 。フォールバック関数は仮想関数にすることも、オーバーライドすることも、修飾子を含めることもできます。fallback () external [payable]
fallback (bytes calldata input) external [payable] returns (bytes memory output)
function
external
スマート コントラクトには、宣言されたフォールバック関数が 1 つだけありfallback () external [payable] 或者fallback (bytes calldata input) external [payable] returns (bytes memory output)
、そのどちらにも function キーワードがありません。コールバック関数は、外部の可視属性である必要があります。コールバック関数は仮想にすることも、書き換えることも、修飾子を使用して変更することもできます。
Contract TestPayable { uint x; uint y; // この関数は 、プレーン イーサ転送を除き、 // このコントラクトに送信されるすべてのメッセージに対して呼び出されます // (受信関数以外の関数はありません) 。このコントラクトへの空の calldata は、 // フォールバック関数を実行します (Ether が呼び出しとともに送信された場合でも)。 // 空でない calldata 呼び出しは、このコールバック関数をトリガーします。 // 呼び出し内にパラメーターがある場合、フォールバックは呼び出されます。パラメータなしで、receive.fallback () を呼び出します。 external payable { x = 1; y = msg.value; } // この関数は、プレーンな Ether 転送、つまり、 // 空の calldata を持つすべての呼び出しに対して呼び出されます。 //空の calldata を呼び出すと、この受信メソッドが呼び出されます。 accept() 外部支払可能 { x = 2; y = メッセージ値; } }
function callTestPayable(TestPayable test) public returns (bool) { (bool success,) = address(test).call(abi.encodeWithSignature("nonExistingFunction()")); 必要(成功); // 結果、test.x は == 1 になり、test.y は 0 になります。 (success,) = address(test).call{value: 1}(abi.encodeWithSignature("nonExistingFunction()")); 必要(成功); // 結果、test.x は == 1 になり、test.y は 1 になります。 // 誰かがそのコントラクトに Ether を送信すると、TestPayable の受信関数が呼び出されます。 // この関数はストレージに書き込むため、 // 単純な ``send`` または ``transfer``。そのため、低レベルの呼び出しを使用する必要があります。 (成功、) = address(test).call{value: 2 ether}(""); 必要(成功); // 結果、test.x は == 2 になり、test.y は 2 ether になります。 true を返します。 }
継承
Solidity はポリモーフィズムを含む多重継承をサポート Solidity は多重継承とマルチモダリティをサポートします。
ポリモーフィズムとは、関数呼び出し (内部および外部) が、継承階層の最も派生したコントラクト内の同じ名前 (およびパラメーター型) の関数を常に実行することを意味します。これは、virtual および override キーワードを使用して、階層内の各関数で明示的に有効にする必要があります。詳細については、「関数のオーバーライド」を参照してください。
ポリモーフィズムとは、関数呼び出し (内部および外部の両方) が、継承階層の最も派生したコントラクト内の同じ名前 (およびパラメーター型) の関数を常に実行することを意味します。この機能は、 virtual および overrideキーワードを使用して、階層内のすべての関数で明示的に有効にする必要があります。詳細については、「関数のオーバーライド」を参照してください。
コントラクトが他のコントラクトからのものである場合、単一のコントラクトのみがブロックチェーン上に作成され、すべてのボックス compiDSE からのコードはコンピューターであるコントラクトです。これは、すべての interzal 呼び出しが機能することを意味します (メッセージ呼び出しではなく JUMP が使用されます) super.f(..)
。コントラクトは他のコントラクトから継承され、チェーン上に 1 つのコントラクトのみが作成され、すべての基本コントラクト コードが新しく作成されたコントラクトにコンパイルされます。これは、基本コントラクトの関数へのすべての内部呼び出しが内部関数呼び出しを使用することを意味します。(super.f(..) はメッセージ呼び出しではなく JUMP を使用します)。
多重継承の例:
Contract PriceFeed は Owned、Destructible、Named("GoldFeed") { function updateInfo(uint newInfo) public { if (msg.sender == owner) info = newInfo; } // ここでは、「override」のみを指定し、「virtual」は指定しません。 // これは、`PriceFeed` から派生したコントラクトが // `destroy` の動作を変更できないことを意味します。 function destroy() public override(Destructible, Named) { Named。破壊(); 関数 get() public view returns(uint r) { 情報を返します。単位 情報; }
// SPDX ライセンス識別子: GPL-3.0
プラグマ堅牢性 >=0.7.0 <0.9.0;
契約所有 {
constructor() { owner = payable(msg.sender); }
支払い義務のある所有者のアドレス。
コントラクト Destructible は所有されています {
function destroy() virtual public {
if
(msg.sender == owner) selfdestruct(owner);
コントラクト
Base1 は Destructible {
function destroy() public virtual override { /* do cleanup 1 */ super.destroy()
;
コントラクト
Base2 は Destructible {
function destroy() public virtual override { /* do cleanup 2 */ super.destroy() ; 関数
destroy() public override(Base1, Base2) { super.destroy(); }
}
コントラクトの最終は
、Base2 {
Base2
が の関数を呼び出す 場合 super
、その基本コントラクトの 1 つでこの関数を呼び出すだけではありません。むしろ、最終的な継承グラフ内の次のベース コントラクトでこの関数を呼び出します Base1.destroy()
(最終的な継承シーケンスは、最も派生したコントラクトから始まることに注意してください: Final、Base2、Base1、Destructible、owned)。
Base2 は、単純に基底クラス内の特定の関数を呼び出すのではなく、スーパーを呼び出します。これは、最終継承ツリーの次の基底クラスのメソッドを呼び出すため、Base1.destory() を呼び出します。注意: 最終的な継承シーケンスは次のとおりです: Final -> Base2 -> Base1 -> Destructible -> 所有。
関数オーバーライド関数の書き換え
オーバーライド関数は、オーバーライドされた関数の可視性を から に変更するだけです external
。 public
オーバーライド関数は、オーバーライドされた関数の可視性を変更するだけです。たとえば、外部からパブリックに変更できます。
可変性は、次の順序に従ってより厳密なものに変更できます: と nonpayable
によってオーバーライドできます 。 でオーバーライドできます 。 は例外であり、他の変更可能性に変更することはできません。view
pure
view
pure
payable
可変性は、次の順序でより厳密な可変性に変更できます: nonpayable は view、pure によってオーバーライドでき、view は pure によってオーバーライドできます。payable は例外であり、いかなる可変性によってもオーバーライドできません。
基本コンストラクターの引数
// SPDX-License-Identifier: GPL-3.0
プラグマ Solidity >=0.7.0 <0.9.0;
コントラクト Base {
uint x;
コンストラクター(uint _x) { x = _x; }
}
// 継承リストで直接指定します...
contract Derived1 is Base(7) {
constructor() {}
}
// または派生コンストラクターの「修飾子」を介して指定します。
コントラクト Derived2 は Base {
constructor(uint _y) Base(_y * _y) {}
}
多重継承と線形化 (多重継承と線形化、右から左へ、Python は左から右へ)
これを説明するもう 1 つの簡単な方法は、異なるコントラクトで複数回定義された関数が呼び出された場合、指定された塩基が深さ優先で右から左 (Python では左から右) に検索され、最初の一致で停止するというものです。 . 基本コントラクトがすでに検索されている場合、それはスキップされます. これを簡単に説明する別の方法は次のとおりです: 多重継承では、複数の異なるコントラクトで定義された関数を呼び出すとき、基本クラス関数の検索順序は右から左になります。深さ優先方式 (Python では左から右)。契約がすでに検索されている場合、検索は中断され、ポップアップ表示されます。
コンストラクター呼び出しの順序に注意してください。
// SP DX-License-Identifier: GPL-3.0 プラグマ堅牢性 >=0.7.0 <0.9.0; Contract Base1 { constructor() {} } contract Base2 { constructor() {} } // コンストラクターは次の順序で実行されます: // 1 - Base1 // 2 - Base2 // 3 - Derived1 Contract Derived1 is Base1, Base2 { constructor() Base1() Base2() {} } // コンストラクターは次の順序で実行されます: // 1 - Base2 // 2 - Base1 // 3 - Derived2 コントラクト Derived2 は Base2, Base1 { constructor() Base2() Base1() {} } // コンストラクターは引き続き次の順序で実行されます。 // 1 - Base2 // 2 - Base1 // 3 - Derived3 コントラクト Derived3 is Base2, Base1 { constructor() Base1() Base2() {} }
抽象的な契約 抽象的な契約
少なくとも 1 つの機能が実装されていない場合、コントラクトは抽象としてマークされる必要があります。
少なくとも 1 つの機能が実装されていない場合、コントラクトは抽象としてマークされる必要があります。
すべての機能が実装されている場合でも、コントラクトは抽象としてマークされる場合があります。
すべての機能が実装されている場合でも、コントラクトは抽象としてマークされる場合があります。
このような抽象コントラクトは直接インスタンス化することはできませんが、これは、抽象コントラクト自体がすべての定義された関数を実装する場合にも当てはまります。
コントラクトが抽象コントラクトを継承し、オーバーライドによって実装されていない機能をすべて実装していない場合は、同様に抽象としてマークする必要があります。
コントラクトが抽象コントラクトを継承し、すべての抽象関数を実装していない場合、そのコントラクトも抽象としてマークする必要があります。
抽象コントラクトでは、抽象関数を使用して実装された関数をオーバーライドすることはできません。
インターフェース
-
他のコントラクトから継承することはできませんが、他のインターフェイスから継承することはできます。
-
宣言された関数はすべて外部である必要があります。宣言された関数はすべて外部である必要があります。
-
コンストラクターを宣言することはできません。コンストラクターを宣言することもできません。
-
状態変数を宣言することはできません。
インターフェイスで宣言されたすべての関数は暗黙的に
virtual
、つまりオーバーライドできることを意味します。これは、オーバーライド関数を再度オーバーライドできることを自動的に意味するわけではありません。これは、オーバーライド関数がマークされている場合にのみ可能ですvirtual
。インターフェイスで定義されたすべての関数は暗黙的に virtual で修飾されます。つまり、オーバーライドできることを意味します。これは自動という意味ではなく、書き換え関数は自動的に書き換えることができ、仮想で装飾する必要があります。
図書館
ライブラリはコントラクトに似ていますが、その目的は、特定のアドレスに 1 回だけデプロイされ、 EVM のDELEGATECALL
( Homestead まで) 機能を使用してコードが再利用されることです。ライブラリとコントラクトは非常に似ており、特定のアドレスにデプロイされます。CALLCODE
上記のコードは、EVM の機能デリゲートコールまたはコールコードを通じて呼び出され、再利用されます。ライブラリの実行環境は、呼び出し元のコントラクトの実行環境です。
ライブラリ関数は直接呼び出しのみ可能 libereries 関数が view または pure の場合のみ、直接呼び出すことができます。状態の変更が許可されていない Lib 関数は直接呼び出され、delegatecall を通じて呼び出す必要があります。
ライブラリは、それを使用するコントラクトの暗黙的な基本コントラクトとみなすことができます。これらは継承階層では明示的には表示されませんが、ライブラリ関数の呼び出しは、明示的な基本コントラクトの関数の呼び出しとまったく同じように見えます ( のような修飾アクセスを使用 L.f()
)。もちろん、内部関数の呼び出しには内部呼び出し規約が使用されます。これは、すべての内部型を渡すことができ、 メモリに格納されている型は コピーされずに参照によって渡されることを意味します。EVM でこれを実現するために、内部ライブラリ関数のコードとそこから呼び出されるすべての関数はコンパイル時に呼び出しコントラクトに含まれ、 の代わりに通常の呼び出しが使用され JUMP
ます DELEGATECALL
。
ライブラリは暗黙的な基本クラスの継承コントラクトと見なすことができますが、 lib は継承階層には現れませんが、 lib を呼び出す関数は基本クラス コントラクトの関数を呼び出すのと同じです (正しいアクセスは Lf() である必要があります)。もちろん、内部関数を呼び出すには、内部関数を呼び出す規則を使用します。規則 規則: 渡されたすべての内部型がメモリに保存され、値のコピーではなく参照によって渡すことができることを意味します。EVM がこの機能を実装するには、lib の内部関数コードとそこで呼び出されるパラメータがコンパイル時に呼び出しコントラクト (呼び出しコントラクト) に組み込まれ、DELEGATECALL が通常の JUMP に置き換えられます。
コンパイラはライブラリがデプロイされるアドレスを知らないため、コンパイルされた 16 進コードには という形式のプレースホルダが含まれます __$30bbc0abd4d6364515865950d3e0d10953$__
。
コンパイラーはライブラリがデプロイされるアドレスを知らないため、コンパイルされたすべての 16 進コードにはプレースホルダー シンボル ( __$30bbc0abd4d6364515865950d3e0d10953$__ (是
libraries/bigint.sol: BigInt のハッシュ形式) が含まれます。)
このようなバイトコードは不完全であるため、デプロイすべきではありません。プレースホルダは実際のアドレスに置き換える必要があります。これを行うには、ライブラリのコンパイル時にプレースホルダをコンパイラに渡すか、リンカーを使用してコンパイル済みのバイナリを更新します。「ライブラリ」を参照してください 。リンク にコマンドライン コンパイラを使用する方法については、リンクを参照してください。このようなバイト コードは不完全であるため、デプロイしないでください。プレースホルダーは実際のアドレスに置き換える必要があります。ライブラリのコンパイル中に実アドレスをコンパイラに渡すことも、リンカーを使用してコンパイル済みのバイトコードを更新して実アドレスにリンクすることもできます。
契約と比較して、ライブラリは次の点で制限されます。
-
状態変数を持つことはできません
-
継承することも継承されることもできません
-
彼らはイーサを受け取ることができません
-
彼らは破壊することができない
(これらは後で解除される可能性があります。) これらの制限は将来解除される可能性があります。
ライブラリの関数シグネチャとセレクター
コントラクト ABI と同様に、セレクターは署名の Keccak256 ハッシュの最初の 4 バイトで構成されます。
図書館向けの通話保護
冒頭で述べたように、ライブラリのコードが または CALL
の代わりに を 使用して実行された場合、 または 関数が呼び出されない限り元に戻ります 。DELEGATECALL
CALLCODE
view
pure
ビュー関数と純粋なライブラリ関数のみを呼び出しで呼び出すことができ、その他の関数は呼び出すことができません。それ以外の場合は元に戻されます。
DelegateCall と callcode はすべてのライブラリ関数を呼び出すことができます。
EVM は、コントラクトが を使用して呼び出されたか CALL
どうかを検出する直接的な方法を提供しませんが、コントラクトは ADDRESS
オペコードを使用して、現在実行されている「場所」を確認できます。生成されたコードは、このアドレスを構築時に使用されたアドレスと比較して、呼び出しモードを決定します。
EVM は、コントラクトがコールで呼び出されたかどうかをコントラクトが識別する直接的な方法を提供しませんが、コントラクトは ADDRESS オペコードを使用して、現在どこで実行されているかを識別できます。生成されたコードは、this.address とコンストラクター内のアドレスを比較して、コントラクト操作モードを識別します。
具体的には、ライブラリのランタイム コードは常にプッシュ命令で始まり、コンパイル時には 20 バイトのゼロになります。デプロイ コードが実行されると、この定数はメモリ内で現在のアドレスに置き換えられ、この変更されたコードはコントラクトに保存されます。これにより、実行時に、デプロイ時のアドレスがスタックにプッシュされる最初の定数となり、ディスパッチャー コードは、非ビュー関数および非純粋関数について、現在のアドレスとこの定数を比較します。
これは、ライブラリのチェーンに格納されている実際のコードが、コンパイラによって として報告されるコードとは異なることを意味します deployedBytecode
。
具体的には、ライブラリのランタイム コードは常にプッシュ構造 (20 バイトの構造、コンパイル中はすべて 0) で始まり、デプロイメントが開始されると、この 20 バイトの定数は現在のアドレスに置き換えられます。そして、この変更されたコードは、契約。実行時、デプロイメント時のアドレスは優先度定数となりスタックにプッシュされ、ディスパッチャ コードは現在のアドレスを非ビューおよび非純粋メソッドごとの定数アドレスと比較します。
これは、ライブラリ チェーンに格納されている実際のコードが、コンパイラがdeployedBytecodeとして報告するコードとは異なることを意味します。
の使用
このディレクティブ を使用すると、コントラクトのコンテキストでusing A for B;
ライブラリ関数 ( library から A
) を任意の型 ( ) にアタッチできます。B
これらの関数は、最初のパラメーターとして呼び出されるオブジェクト ( self
Python の変数など) を受け取ります。
A を B に直接使用すると、ライブラリ A のすべての関数をコントラクト B に割り当てることができます。これらのライブラリ関数は B を最初のパラメータとして受け取ります。
その結果、 using A for *;
ライブラリの関数が任意 の型A
に関連付けられます 。
* に A を使用すると、ライブラリ A 関数がすべての型に付加されます。
この using A for B;
ディレクティブは、そのすべての機能内を含む現在のコントラクト内でのみ有効であり、それが使用されているコントラクトの外では効果がありません。ディレクティブはコントラクト内でのみ使用でき、その関数内では使用できません。
using A for B コマンドは、コントラクト内のすべての機能を含むコントラクト内でのみ有効であり、コントラクトの外部には影響しません。この命令はコントラクト内にのみ記述でき、関数内には記述できません。
すべての外部ライブラリ呼び出しは実際の EVM 関数呼び出しであることに注意してください。これは、メモリまたは値の型を渡すと、変数であってもコピーが実行されることを意味します self
。コピーが実行されない唯一の状況は、ストレージ参照変数が使用される場合、または内部ライブラリ関数が呼び出される場合です。
すべての外部ライブラリ呼び出しは実際の EVM 関数呼び出しであることに注意してください。これは、メモリまたは値型が渡された場合、引数であってもコピーが実行されることを意味します。コピーが実行されない唯一のケースは、格納された参照変数を使用する場合、または内部ライブラリ関数を呼び出す場合です。
ストレージ タイプを除くライブラリの外部関数の呼び出しは、すべて値のコピーです。ライブラリ内部関数の呼び出しはすべて値による参照です。
インラインアセンブリ
インライン アセンブリは、イーサリアム仮想マシンに低レベルでアクセスする方法です。これにより、Solidity のいくつかの重要な安全機能とチェックがバイパスされます。このバイパスは、Solidity のセキュリティ機能とセキュリティ チェックにおいて非常に重要です。必要なタスクにのみ使用し、使用に自信がある場合にのみ使用してください。タスクで本当に必要な場合にのみ使用し、必ず使用する必要があります。
インライン アセンブリ ブロックは次のようにマークされます。 assembly { ... }