イーサリアムオラクル契約:Solidityコード機能
公開: 2022-03-11この3部構成の最初のセグメントでは、オラクルとの単純な契約ペアを提供する小さなチュートリアルを実行しました。 セットアップ(トリュフを使用)、コードのコンパイル、テストネットワークへのデプロイ、実行、およびデバッグのメカニズムとプロセスについて説明しました。 ただし、コードの詳細の多くは、手で波打つようにまとめられています。 そこで、約束どおり、Solidityスマートコントラクト開発に固有であり、この特定のコントラクトオラクルシナリオに固有の言語機能のいくつかを見ていきます。 すべての詳細を入念に調べることはできませんが(必要に応じて、今後の調査でそれをお任せします)、コードの最も印象的で、最も興味深く、最も重要な機能を見つけようとします。
これを容易にするために、プロジェクトの独自のバージョン(ある場合)を開くか、コードを参照できるようにしておくことをお勧めします。
この時点での完全なコードはここにあります:https://github.com/jrkosinski/oracle-example/tree/part2-step1
イーサリアムとソリディティ
Solidityは利用可能なスマートコントラクト開発言語だけではありませんが、Ethereumスマートコントラクトにとって最も一般的で一般的に最も人気があると言っても過言ではありません。 確かに、この記事の執筆時点で最も人気のあるサポートと情報を持っているのはそれです。
Solidityはオブジェクト指向でチューリング完全です。 とは言うものの、組み込みの(そして完全に意図的な)制限にすぐに気付くでしょう。これにより、スマートコントラクトプログラミングは通常のレッツドゥザシングハッキングとはまったく異なる感じになります。
Solidityバージョン
すべてのSolidityコード詩の最初の行は次のとおりです。
pragma solidity ^0.4.17;
Solidityはまだ若いうちに変化し、急速に進化しているため、表示されるバージョン番号は異なります。 バージョン0.4.17は、私の例で使用したバージョンです。 この発行時の最新バージョンは0.4.25です。
これを読んでいる現時点での最新バージョンは、まったく異なるものである可能性があります。 多くの優れた機能がSolidityの作業中(または少なくとも計画中)にあり、これについては現在説明します。
さまざまなSolidityバージョンの概要は次のとおりです。
上級者向けのヒント:次のように、バージョンの範囲を指定することもできます(ただし、これが頻繁に行われることはありません)。
pragma solidity >=0.4.16 <0.6.0;
Solidityプログラミング言語の機能
Solidityには、ほとんどの現代のプログラマーに馴染みのある多くの言語機能と、(少なくとも私にとっては)独特で珍しいものがあります。 C ++、Python、JavaScriptに触発されたと言われています。これらはすべて私にはよく知られていますが、Solidityはこれらの言語とはまったく異なるようです。
契約
.solファイルはコードの基本単位です。 BoxingOracle.solで、9行目に注意してください。
contract BoxingOracle is Ownable {
クラスはオブジェクト指向言語のロジックの基本単位であるため、コントラクトはSolidityのロジックの基本単位です。 今のところ、コントラクトはSolidityの「クラス」であると言えば十分です(オブジェクト指向プログラマーにとって、これは簡単な飛躍です)。
継承
Solidityコントラクトは継承を完全にサポートし、期待どおりに機能します。 プライベート契約メンバーは継承されませんが、保護されたメンバーとパブリック契約メンバーは継承されます。 予想どおり、オーバーロードとポリモーフィズムがサポートされています。
contract BoxingOracle is Ownable {
上記のステートメントで、「is」キーワードは継承を示しています。 BoxingOracleはOwnableから継承します。 Solidityでは多重継承もサポートされています。 多重継承は、次のように、クラス名のコンマ区切りリストで示されます。
contract Child is ParentA, ParentB, ParentC { …
(私の意見では)継承モデルを構築するときに過度に複雑にするのは良い考えではありませんが、いわゆるダイヤモンド問題に関するSolidityに関する興味深い記事があります。
列挙型
列挙型はSolidityでサポートされています:
enum MatchOutcome { Pending, //match has not been fought to decision Underway, //match has started & is underway Draw, //anything other than a clear winner (eg, cancelled) Decided //index of participant who is the winner }
ご想像のとおり(使い慣れた言語と同じです)、各列挙値には0から始まる整数値が割り当てられます。Solidityのドキュメントに記載されているように、列挙値はすべての整数型(uint、uint16、uint32、など)が、暗黙の変換は許可されていません。 つまり、明示的にキャストする必要があります(たとえば、uintに)。
Solidity Docs:列挙型列挙型チュートリアル
構造体
構造体は、列挙型のように、ユーザー定義のデータ型を作成する別の方法です。 構造体は、すべてのC /C++ファンデーションコーダーと私のような老人によく知られています。 BoxingOracle.solの17行目からの構造体の例:
//defines a match along with its outcome struct Match { bytes32 id; string name; string participants; uint8 participantCount; uint date; MatchOutcome outcome; int8 winner; }
すべての古いCプログラマーへの注意: Solidityでの構造体の「パッキング」は重要ですが、いくつかのルールと警告があります。 必ずしもCと同じように機能するとは限りません。 ドキュメントを確認し、状況を認識して、特定のケースでパッキングが役立つかどうかを確認してください。
SolidityStructパッキング
作成された構造体は、コード内でネイティブデータ型としてアドレス指定できます。 上記で作成した構造体タイプの「インスタンス化」の構文の例を次に示します。
Match match = Match(id, "A vs. B", "A|B", 2, block.timestamp, MatchOutcome.Pending, 1);
Solidityのデータ型
これにより、Solidityのデータ型の非常に基本的な主題にたどり着きます。 Solidityはどのデータ型をサポートしていますか? Solidityは静的に型付けされており、この記事の執筆時点では、データ型を明示的に宣言して変数にバインドする必要があります。
Solidityデータ型
ブール値
ブール型はboolという名前でサポートされており、値はtrueまたはfalseです。
数値タイプ
整数型は、int8/uint8からint256/uint256(それぞれ、8ビット整数から256ビット整数)まで、符号付きと符号なしの両方でサポートされています。 タイプuintはuint256の省略形です(同様にintはint256の省略形です)。
特に、浮動小数点型はサポートされていません。 なぜだめですか? 一つには、金銭的価値を扱う場合、浮動小数点変数は(もちろん一般的に)悪い考えであることがよく知られています。なぜなら、価値は空中に失われる可能性があるからです。 エーテル値はweiで表されます。これは、エーテルの1 / 1,000,000,000,000,000,000であり、すべての目的に十分な精度である必要があります。 エーテルを小さな部分に分解することはできません。
現時点では、固定小数点値は部分的にサポートされています。 Solidityのドキュメントによると、 「固定小数点数はまだSolidityで完全にはサポートされていません。 宣言することはできますが、割り当てたり、割り当てたりすることはできません。」
https://hackernoon.com/a-note-on-numbers-in-ethereum-and-javascript-3e6ac3b2fad9
注:ほとんどの場合、変数のサイズを小さくすると(たとえば、uint32に)、ガスコストが予想どおりに減少するのではなく、実際に増加する可能性があるため、uintを使用するのが最適です。 一般的な経験則として、他の方法で行う正当な理由があることが確実でない限り、uintを使用してください。
文字列型
Solidityの文字列データ型は面白いテーマです。 誰と話すかによって意見が異なる場合があります。 Solidityには文字列データ型があります。それは事実です。 私の意見は、おそらくほとんどの人に共有されていますが、それは多くの機能を提供していないということです。 文字列の解析、連結、置換、トリミング、さらには文字列の長さのカウントもあります。文字列型に期待されるものはどれも存在しないため、それらはユーザーの責任です(必要な場合)。 文字列の代わりにbytes32を使用する人もいます。 それもできます。
Solidity文字列に関する楽しい記事
私の意見:独自の文字列型を作成して一般的に使用できるように公開するのは楽しい練習になるかもしれません。
アドレスタイプ
おそらくSolidityに固有の、特にイーサリアムウォレットまたは契約アドレス用のアドレスデータ型があります。 これは、その特定のサイズのアドレスを格納するための20バイトの値です。 さらに、その種類のアドレス専用のタイプメンバーがあります。
address internal boxingOracleAddr = 0x145ca3e014aaf5dca488057592ee45305d9b3a22;
住所データ型
日時タイプ
たとえばJavaScriptの場合のように、Solidity自体にはネイティブのDateまたはDateTimeタイプはありません。 (ああ、段落ごとにSolidityのサウンドはどんどん悪くなっています!?)日付は、uint(uint256)タイプのタイムスタンプとしてネイティブにアドレス指定されます。 ブロックタイムスタンプはUnixスタイルのタイムスタンプであるため、通常、ミリ秒ではなく秒単位でUnixスタイルのタイムスタンプとして処理されます。 さまざまな理由で人間が読める形式の日付が必要な場合は、オープンソースのライブラリを利用できます。 BoxingOracleで使用したことがあるかもしれません:DateLib.sol。 OpenZeppelinには、日付ユーティリティや他の多くのタイプの一般的なユーティリティライブラリもあります(Solidityのライブラリ機能については後ほど説明します)。
上級者向けのヒント: OpenZeppelinは、契約の構築に役立つ可能性のある知識と事前に作成されたジェネリックコードの両方にとって優れたソースです(もちろん、唯一の優れたソースではありません)。
マッピング
BoxingOracle.solの11行目で、マッピングと呼ばれるものが定義されていることに注意してください。
mapping(bytes32 => uint) matchIdToIndex;
Solidityのマッピングは、クイックルックアップ用の特別なデータ型です。 基本的にルックアップテーブルまたはハッシュテーブルに類似しており、含まれるデータはブロックチェーン自体に存在します(マッピングがここにあるようにクラスメンバーとして定義されている場合)。 コントラクトの実行中に、ハッシュテーブルにデータを追加するのと同じように、マッピングにデータを追加し、後で追加した値を検索できます。 この場合、追加するデータはブロックチェーン自体に追加されるため、保持されることに注意してください。 今日ニューヨークのマッピングに追加すると、今から1週間後にイスタンブールの誰かがそれを読むことができます。
BoxingOracle.solの71行目からのマッピングへの追加の例:
matchIdToIndex[id] = newIndex+1
BoxingOracle.solの51行目からのマッピングからの読み取りの例:
uint index = matchIdToIndex[_matchId];
アイテムをマッピングから削除することもできます。 このプロジェクトでは使用されていませんが、次のようになります。
delete matchIdToIndex[_matchId];
戻り値
お気づきかもしれませんが、SolidityはJavascriptに表面的に似ているかもしれませんが、JavaScriptの型と定義の緩みの多くを継承していません。 コントラクトコードは、かなり厳密で制限された方法で定義する必要があります(ユースケースを考えると、これはおそらく良いことです)。 そのことを念頭に置いて、BoxingOracle.solの40行目の関数定義を検討してください。
function _getMatchIndex(bytes32 _matchId) private view returns (uint) { ... }
では、まず、ここに含まれているものの概要を簡単に説明しましょう。 function
はそれを関数としてマークします。 _getMatchIndex
は関数名です(アンダースコアはプライベートメンバーを示す規則です。これについては後で説明します)。 これは、 bytes32
型の_matchId
(今回はアンダースコア規則を使用して関数の引数を表す)という名前の1つの引数を取ります。 キーワードprivate
は、実際にはメンバーをスコープ内でprivateにし、 view
は、この関数がブロックチェーン上のデータを変更しないことをコンパイラーに通知し、最後に:~~~ solidity return(uint)~~~
これは、関数がuintを返すことを示しています(voidを返す関数は、ここreturns
句がないだけです)。 括弧内にuintがあるのはなぜですか? これは、Solidity関数がタプルを返すことができるためです。
ここで、166行目の次の定義について考えてみます。
function getMostRecentMatch(bool _pending) public view returns ( bytes32 id, string name, string participants, uint8 participantCount, uint date, MatchOutcome outcome, int8 winner) { ... }
これのreturn句をチェックしてください! 1、2…7つの異なるものを返します。 OK、それで、この関数はこれらのものをタプルとして返します。 なんで? 開発の過程で、構造体を返す必要があることに気付くことがよくあります(JavaScriptの場合は、おそらくJSONオブジェクトを返したいと思うでしょう)。 さて、この記事の執筆時点では(将来的には変更される可能性がありますが)、Solidityはパブリック関数からの構造体の戻りをサポートしていません。 したがって、代わりにタプルを返す必要があります。 あなたがPythonの人なら、すでにタプルに慣れているかもしれません。 ただし、少なくともこの方法では、多くの言語は実際にはそれらをサポートしていません。
タプルを戻り値として返す例については、159行目を参照してください。
return (_matchId, "", "", 0, 0, MatchOutcome.Pending, -1);
そして、このようなものの戻り値をどのように受け入れるのでしょうか? 私たちはそうすることができます:
var (id, name, part, count, date, outcome, winner) = getMostRecentMatch(false);
または、変数を正しいタイプで事前に明示的に宣言することもできます。
//declare the variables bytes32 id; string name; ... etc... int8 winner; //assign their values (id, name, part, count, date, outcome, winner) = getMostRecentMatch(false);
これで、7つの戻り値を保持する7つの変数を宣言しました。これを使用できます。 それ以外の場合、値の1つまたは2つだけが必要であるとすると、次のように言うことができます。
//declare the variables bytes32 id; uint date; //assign their values (id,,,,date,,) = getMostRecentMatch(false);
私たちがそこで何をしたかわかりますか? 興味のある2つだけを取得しました。これらのコンマをすべてチェックしてください。 慎重に数える必要があります!
輸入
BoxingOracle.solの3行目と4行目はインポートです。
import "./Ownable.sol"; import "./DateLib.sol";
ご想像のとおり、これらはBoxingOracle.solと同じコントラクトプロジェクトフォルダに存在するコードファイルから定義をインポートしています。
修飾子
関数定義には多数の修飾子が付加されていることに注意してください。 まず、可視性があります。プライベート、パブリック、内部、および外部の機能の可視性です。
さらに、 pure
キーワードとview
されます。 これらは、関数がどのような変更を行うかをコンパイラーに示します。 このようなことは、関数を実行するための最終的なガスコストの要因であるため、これは重要です。 説明については、ここを参照してください:SolidityDocs。
最後に、私が本当に話したいのは、カスタム修飾子です。 BoxingOracle.solの61行目をご覧ください。
function addMatch(string _name, string _participants, uint8 _participantCount, uint _date) onlyOwner public returns (bytes32) {
「public」キーワードの直前にあるonlyOwner
修飾子に注意してください。 これは、コントラクトの所有者のみがこのメソッドを呼び出すことができることを示しています。 非常に重要ですが、これはSolidityのネイティブ機能ではありません(将来的にはそうなるかもしれませんが)。 実際、 onlyOwner
は、自分で作成して使用するカスタム修飾子の例です。 みてみましょう。
まず、修飾子がファイルOwnable.solで定義されています。このファイルは、BoxingOracle.solの3行目にインポートされていることがわかります。
import "./Ownable.sol"
モディファイアを利用するために、 Ownable
がBoxingOracle
から継承するようにしたことに注意してください。 Ownable.solの25行目に、「Ownable」コントラクト内の修飾子の定義があります。
modifier onlyOwner() { require(msg.sender == owner); _; }
(ちなみに、この所有可能な契約は、OpenZeppelinの公開契約の1つから取得されます。)
これは修飾子として宣言されていることに注意してください。これは、関数を変更するために、これをそのまま使用できることを示しています。 修飾子の要点は「require」ステートメントであることに注意してください。 Requireステートメントは、assertのようなものですが、デバッグ用ではありません。 requireステートメントの条件が失敗した場合、関数は例外をスローします。 したがって、この「require」ステートメントを言い換えると、次のようになります。
require(msg.sender == owner);
私たちはそれが意味すると言うことができます:

if (msg.send != owner) throw an exception;
実際、Solidity 0.4.22以降では、requireステートメントにエラーメッセージを追加できます。
require(msg.sender == owner, "Error: this function is callable by the owner of the contract, only");
最後に、好奇心旺盛なラインで:
_;
アンダースコアは、「ここで、変更された関数の全内容を実行する」の省略形です。 したがって、実際には、requireステートメントが最初に実行され、次に実際の関数が実行されます。 つまり、このロジック行を変更された関数の前に追加するようなものです。
もちろん、修飾子を使用して実行できることは他にもあります。 ドキュメントを確認してください:ドキュメント。
Solidityライブラリ
ライブラリと呼ばれるSolidityの言語機能があります。 DateLib.solのプロジェクトに例があります。
これは、日付タイプをより簡単に処理するためのライブラリです。 4行目でBoxingOracleにインポートされます。
import "./DateLib.sol";
そしてそれは13行目で使用されています:
using DateLib for DateLib.DateTime;
DateLib.DateTime
は、DateLibコントラクト(メンバーとして公開されています。DateLib.solの4行目を参照)からエクスポートされた構造体であり、特定のデータ型にDateLibライブラリを「使用」していることをここで宣言しています。 したがって、そのライブラリで宣言されたメソッドと操作は、私たちがそうすべきだと言ったデータ型に適用されます。 これが、Solidityでライブラリが使用される方法です。
より明確な例については、SafeMathなどのOpenZeppelinのライブラリのいくつかをチェックしてください。 これらはネイティブ(数値)Solidityデータ型に適用でき(ここではカスタムデータ型にライブラリを適用しました)、広く使用されています。
インターフェース
主流のオブジェクト指向言語と同様に、インターフェースがサポートされています。 Solidityのインターフェースはコントラクトとして定義されていますが、関数の本体は省略されています。 インターフェイス定義の例については、OracleInterface.solを参照してください。 この例では、インターフェースはoracleコントラクトの代用として使用され、そのコンテンツは別個のアドレスを持つ別個のコントラクトに存在します。
命名規則
もちろん、命名規則はグローバルなルールではありません。 プログラマーとして、私たちは私たちにアピールするコーディングと命名規則に自由に従うことができることを知っています。 一方で、私たちは他の人に私たちのコードを読んで操作することを快適に感じてもらいたいので、ある程度の標準化が望まれます。
プロジェクトの概要
問題のコードファイルに存在するいくつかの一般的な言語機能について説明したので、このプロジェクトのコード自体をより具体的に見ていきましょう。
それでは、もう一度、このプロジェクトの目的を明確にしましょう。 このプロジェクトの目的は、半現実的(または疑似現実的)なデモンストレーションと、オラクルを使用するスマートコントラクトの例を提供することです。 本質的に、これは別の別の契約を呼び出す契約にすぎません。
この例のビジネスケースは、次のように説明できます。
- ユーザーは、ボクシングの試合でさまざまなサイズの賭けをしたいと考えており、賭けにお金(エーテル)を支払い、勝った場合は勝った場合に賞金を集めます。
- ユーザーはスマートコントラクトを介してこれらの賭けをします。 (実際のユースケースでは、これはweb3フロントエンドを備えた完全なDAppになりますが、契約側のみを調査しています。)
- 別のスマートコントラクト(オラクル)は、サードパーティによって維持されています。 その仕事は、ボクシングの試合のリストを現在の状態(保留中、進行中、終了など)で維持し、終了した場合は勝者を維持することです。
- メインコントラクトは、オラクルから保留中の一致のリストを取得し、これらを「賭け可能な」一致としてユーザーに提示します。
- 主契約は試合開始までの賭けを受け付けます。
- 試合が決まった後、メインコントラクトは単純なアルゴリズムに従って勝ち負けを分割し、自分でカットし、リクエストに応じて勝ち金を支払います(敗者は単に賭け金全体を失います)。
賭けのルール:
- 定義された最小の賭けがあります(weiで定義されています)。
- 最大の賭けはありません。 ユーザーは、最低額を超えて好きな金額を賭けることができます。
- ユーザーは、試合が「進行中」になるまで賭けをすることができます。
賞金を分割するためのアルゴリズム:
- 受け取ったすべての賭けは「ポット」に入れられます。
- 家のために、ポットからわずかな割合が削除されます。
- 各勝者には、ベットの相対的なサイズに正比例したポットの割合が与えられます。
- 賞金は、試合が決定された後、最初のユーザーが結果を要求するとすぐに計算されます。
- 賞金は、ユーザーの要求に応じて授与されます。
- 引き分けの場合、誰も勝ちません。誰もが賭け金を取り戻し、家はカットされません。
BoxingOracle:Oracle契約
提供される主な機能
オラクルには2つのインターフェースがあります。1つは契約の「所有者」と保守者に提示され、もう1つは一般の人々に提示されます。 つまり、オラクルを消費する契約です。 メンテナは、データをコントラクトにフィードする機能を提供します。基本的に、外部からデータを取得してブロックチェーンに配置します。 一般に、それは上記のデータへの読み取り専用アクセスを提供します。 契約自体が非所有者によるデータの編集を制限していることに注意することが重要ですが、そのデータへの読み取り専用アクセスは制限なしに公に許可されています。
ユーザーへ:
- すべての一致を一覧表示
- 保留中の一致を一覧表示
- 特定の試合の詳細を取得する
- 特定の試合のステータスと結果を取得する
所有者へ:
- 試合を入力してください
- 試合のステータスを変更する
- 試合の結果を設定する
ユーザーストーリー:
- 新しいボクシングの試合が発表され、5月9日に確認されます。
- 契約のメンテナである私(おそらく私は有名なスポーツネットワークまたは新しいアウトレットです)は、ブロックチェーン上のオラクルのデータに「保留中」のステータスで次の試合を追加します。 これで、誰でも、またはどの契約でも、このデータを好きなように照会して使用できます。
- 試合が始まると、その試合のステータスを「進行中」に設定します。
- 試合が終了したら、試合のステータスを「完了」に設定し、試合データを変更して勝者を示します。
Oracleコードレビュー
このレビューは完全にBoxingOracle.solに基づいています。 行番号はそのファイルを参照します。
10行目と11行目で、一致する場所を宣言します。
Match[] matches; mapping(bytes32 => uint) matchIdToIndex;
matches
は、一致インスタンスを格納するための単純な配列であり、マッピングは、一意の一致ID(bytes32値)を配列内のインデックスにマッピングするための単なる機能であり、誰かが一致の生のIDを私たちに渡した場合に次のことができます。このマッピングを使用して見つけます。
17行目では、一致構造が定義され、説明されています。
//defines a match along with its outcome struct Match { bytes32 id; //unique id string name; //human-friendly name (eg, Jones vs. Holloway) string participants; //a delimited string of participant names uint8 participantCount; //number of participants (always 2 for boxing matches!) uint date; //GMT timestamp of date of contest MatchOutcome outcome; //the outcome (if decided) int8 winner; //index of the participant who is the winner } //possible match outcomes enum MatchOutcome { Pending, //match has not been fought to decision Underway, //match has started & is underway Draw, //anything other than a clear winner (eg, cancelled) Decided //index of participant who is the winner }
61行目:関数addMatch
は、契約所有者のみが使用できます。 保存されたデータに新しい一致を追加できます。
80行目:関数declareOutcome
を使用すると、契約所有者は一致を「決定済み」として設定し、勝った参加者を設定できます。
行102-166:次の関数はすべて一般から呼び出すことができます。 これは、一般に公開されている読み取り専用データです。
- 関数
getPendingMatches
は、現在の状態が「保留中」であるすべての一致のIDのリストを返します。 - 関数
getAllMatches
は、すべての一致のIDのリストを返します。 - 関数
getMatch
は、IDで指定された単一の一致の完全な詳細を返します。
行193〜204は、主にテスト、デバッグ、および診断用の関数を宣言しています。
- 関数
testConnection
は、コントラクトを呼び出すことができることをテストするだけです。 - 関数
getAddress
は、このコントラクトのアドレスを返します。 - 関数
addTestData
は、一連のテスト一致を一致リストに追加します。
次の手順に進む前に、コードを少し調べてみてください。 (このシリーズのパート1で説明されているように)デバッグモードでOracleコントラクトを再度実行し、さまざまな関数を呼び出して、結果を確認することをお勧めします。
BoxingBets:クライアント契約
クライアント契約(賭け契約)が何に責任があり、何に責任がないかを定義することが重要です。 クライアント契約は、実際のボクシングの試合のリストを維持したり、その結果を宣言したりする責任を負いません。 私たちはそのサービスのオラクルを「信頼」します(そうです、そのデリケートな言葉があります。これについてはパート3で説明します)。 クライアント契約は、賭けを受け入れる責任があります。 賞金を分割し、試合の結果(オラクルから受け取ったもの)に基づいて勝者のアカウントに転送するアルゴリズムを担当します。
さらに、すべてがプルベースであり、イベントやプッシュはありません。 コントラクトは、オラクルからデータをプルします。 コントラクトは(ユーザーの要求に応じて)オラクルから試合の結果を引き出し、コントラクトは賞金を計算し、ユーザーの要求に応じてそれらを転送します。
提供される主な機能
- 保留中の一致をすべて一覧表示
- 特定の試合の詳細を取得する
- 特定の試合のステータスと結果を取得する
- 賭けをする
- 賞金のリクエスト/受け取り
クライアントコードレビュー
このレビューは完全にBoxingBets.solに基づいています。 行番号はそのファイルを参照します。
コントラクトのコードの最初の行である12行目と13行目は、コントラクトのデータを格納するいくつかのマッピングを定義しています。
12行目は、ユーザーアドレスをIDのリストにマップします。 これは、ユーザーを、そのユーザーに属する賭けのIDのリストにマッピングしています。 したがって、特定のユーザーアドレスについて、そのユーザーが行ったすべての賭けのリストをすばやく取得できます。
mapping(address => bytes32[]) private userToBets;
13行目は、マッチの一意のIDをベットインスタンスのリストにマップします。 これにより、任意の試合について、その試合に対して行われたすべての賭けのリストを取得できます。
mapping(bytes32 => Bet[]) private matchToBets;
17行目と18行目は、オラクルへの接続に関連しています。 まず、 boxingOracleAddr
変数に、オラクルコントラクトのアドレスを格納します(デフォルトではゼロに設定されています)。 オラクルのアドレスをハードコーディングすることはできますが、変更することはできません。 (オラクルのアドレスを変更できないことは、良いことでも悪いことでもあります。これについては、パート3で説明します)。 次の行は、オラクルのインターフェース(OracleInterface.solで定義されている)のインスタンスを作成し、それを変数に格納します。
//boxing results oracle address internal boxingOracleAddr = 0; OracleInterface internal boxingOracle = OracleInterface(boxingOracleAddr);
58行目にジャンプすると、 setOracleAddress
関数が表示されます。この関数では、このOracleアドレスを変更でき、 boxingOracle
インスタンスが新しいアドレスで再インスタンス化されます。
21行目は、最小ベットサイズをweiで定義しています。 もちろん、これは実際には非常に少量で、0.000001エーテルです。
uint internal minimumBet = 1000000000000;
58行目と66行目には、それぞれsetOracleAddress
関数とgetOracleAddress
関数があります。 コントラクトの所有者のみがOracleを別のOracleに切り替えることができるため、 setOracleAddress
にはonlyOwner
修飾子があります(おそらく良い考えではありませんが、パート3で詳しく説明します)。 一方、 getOracleAddress
関数は、パブリックに呼び出すことができます。 誰もがどのオラクルが使用されているかを見ることができます。
function setOracleAddress(address _oracleAddress) external onlyOwner returns (bool) {... function getOracleAddress() external view returns (address) { ....
72行目と79行目には、それぞれgetBettableMatches
関数とgetMatch
関数があります。 これらは単に呼び出しをオラクルに転送し、結果を返すことに注意してください。
function getBettableMatches() public view returns (bytes32[]) {... function getMatch(bytes32 _matchId) public view returns ( ....
placeBet
関数は非常に重要です(108行目)。
function placeBet(bytes32 _matchId, uint8 _chosenWinner) public payable { ...
これの顕著な特徴は、 payable
なモディファイアです。 私たちは一般的な言語機能について話し合うのに忙しいので、関数呼び出しと一緒に送金できるという中心的に重要な機能にはまだ触れていません! それが基本的にはそれであり、他の引数や送信されたデータと一緒に金額を受け入れることができる関数です。
これが必要なのは、ユーザーがどの賭けをするか、その賭けにどれだけのお金を乗せるかを同時に定義し、実際に送金する場所だからです。 payable
修飾子はそれを可能にします。 賭けを受け入れる前に、賭けの有効性を確認するために一連のチェックを行います。 111行目の最初のチェックは次のとおりです。
require(msg.value >= minimumBet, "Bet amount must be >= minimum bet");
送金金額はmsg.value
に保存されます。 すべてのチェックに合格したと仮定すると、123行目で、その金額をオラクルの所有権に移し、その金額の所有権をユーザーから奪い、契約の所有権に移します。
address(this).transfer(msg.value);
最後に、136行目には、コントラクトが有効なオラクルに接続されているかどうかを知るのに役立つテスト/デバッグヘルパー関数があります。
function testOracleConnection() public view returns (bool) { return boxingOracle.testConnection(); }
まとめ
そして、これは実際にはこの例の範囲内です。 ただ賭けを受け入れる。 賞金を分割して支払う機能、およびその他のロジックは、契約でのオラクルの使用を示すだけの目的のために例を十分に単純にするために、意図的に省略されています。 そのより完全で複雑なロジックは、現在別のプロジェクトに存在します。これは、この例の拡張であり、まだ開発中です。
これで、コードベースについての理解が深まり、Solidityが提供する言語機能のいくつかを議論するための手段および出発点として使用できるようになりました。 この3部構成のシリーズの主な目的は、オラクルとの契約の使用について説明し、説明することです。 このパートの目的は、この特定のコードをもう少しよく理解し、Solidityとスマートコントラクト開発のいくつかの機能を理解するための出発点として使用することです。 3番目の最後の部分の目的は、オラクルの使用の戦略と哲学、およびそれがスマートコントラクトモデルに概念的にどのように適合するかについて説明することです。
その他のオプションの手順
もっと学びたい読者には、このコードを使って遊んでみることを強くお勧めします。 新しい機能を実装します。 バグを修正します。 実装されていない機能(支払いインターフェースなど)を実装します。 関数呼び出しをテストします。 それらを変更し、再テストして何が起こるかを確認します。 web3フロントエンドを追加します。 一致を削除したり、結果を変更したりするための機能を追加します(間違いの場合)。 キャンセルされた試合はどうですか? 2番目のOracleを実装します。 もちろん、契約は好きなだけオラクルを自由に使用できますが、それはどのような問題を引き起こしますか? それを楽しんでください。 それは学ぶための素晴らしい方法です、そしてあなたがそのようにそれをするとき(そしてそれから楽しみを引き出すとき)あなたはあなたが学んだことのより多くを保持することは確実です。
試すべきことのサンプル、包括的でないリスト:
- ローカルテストネット(パート1で説明されているようにトリュフ)でコントラクトとオラクルの両方を実行し、すべての呼び出し可能関数とすべてのテスト関数を呼び出します。
- 試合の完了時に、賞金を計算して支払うための機能を追加します。
- 引き分けの場合にすべての賭けを払い戻すための機能を追加します。
- 試合が始まる前に、賭けの払い戻しまたはキャンセルを要求する機能を追加します。
- 試合がキャンセルされることがあるという事実を考慮に入れる機能を追加します(その場合、全員が払い戻しを必要とします)。
- ユーザーが賭けをしたときに配置されていたオラクルが、その一致の結果を決定するために使用されるオラクルと同じであることを保証する機能を実装します。
- 別の(2番目の)オラクルを実装します。これにはいくつかの異なる機能が関連付けられているか、ボクシング以外のスポーツを提供する可能性があります(参加者の数とリストではさまざまな種類のスポーツが許可されているため、実際にはボクシングだけに限定されません) 。
-
getMostRecentMatch
を実装して、最後に追加された一致、または発生する日付に関して現在の日付に最も近い一致を実際に返すようにします。 - 例外処理を実装します。
契約とオラクルの関係の仕組みに慣れたら、この3部構成のシリーズのパート3で、この例で提起された戦略、設計、および哲学的問題のいくつかについて説明します。