สัญญา Ethereum Oracle: คุณสมบัติรหัสความแข็งแกร่ง

เผยแพร่แล้ว: 2022-03-11

ในส่วนแรกของสามส่วนนี้ เราได้อ่านบทช่วยสอนเล็กๆ น้อยๆ ที่ให้คู่สัญญากับออราเคิลง่ายๆ แก่เรา มีการอธิบายกลไกและกระบวนการของการตั้งค่า (ด้วยทรัฟเฟิล) การคอมไพล์โค้ด การปรับใช้กับเครือข่ายทดสอบ การรัน และการดีบั๊ก อย่างไรก็ตาม รายละเอียดจำนวนมากของรหัสถูกกลบด้วยลักษณะหยักด้วยมือ ตามที่ได้สัญญาไว้ เราจะมาดูคุณสมบัติทางภาษาบางอย่างซึ่งมีความพิเศษเฉพาะสำหรับการพัฒนา Solidity smart contract และมีความพิเศษเฉพาะในสถานการณ์ของสัญญา-oracle โดยเฉพาะ แม้ว่าเราจะดูไม่ได้อย่างละเอียดถี่ถ้วน (ฉันจะฝากไว้ให้คุณในการศึกษาต่อ ถ้าคุณต้องการ) เราจะพยายามใช้คุณลักษณะที่โดดเด่นที่สุด น่าสนใจที่สุด และสำคัญที่สุดของโค้ด

เพื่อความสะดวกในการดำเนินการนี้ เราขอแนะนำให้คุณเปิดเวอร์ชันของโปรเจ็กต์ของคุณเอง (หากมี) หรือมีโค้ดสำหรับใช้อ้างอิง

รหัสเต็ม ณ จุดนี้สามารถพบได้ที่นี่: https://github.com/jrkosinski/oracle-example/tree/part2-step1

Ethereum และความแข็งแกร่ง

Solidity ไม่ได้เป็นเพียงภาษาเดียวสำหรับการพัฒนาสัญญาอัจฉริยะ แต่ฉันคิดว่ามันปลอดภัยพอที่จะบอกว่าเป็นภาษาที่ใช้กันทั่วไปและเป็นที่นิยมมากที่สุดสำหรับ Ethereum smart contract แน่นอนว่าเป็นผู้ที่ได้รับการสนับสนุนและข้อมูลที่ได้รับความนิยมมากที่สุดในขณะที่เขียนบทความนี้

ไดอะแกรมของคุณสมบัติ 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 {

ในคำสั่งข้างต้น คีย์เวิร์ด "คือ" หมายถึงการสืบทอด BoxingOracle สืบทอดมาจาก Ownable รองรับการสืบทอดหลายรายการใน Solidity การสืบทอดหลายรายการถูกระบุโดยรายชื่อคลาสที่คั่นด้วยเครื่องหมายจุลภาค เช่น:

 contract Child is ParentA, ParentB, ParentC { …

ในขณะที่ (ในความคิดของฉัน) ไม่ใช่ความคิดที่ดีที่จะซับซ้อนเกินไปเมื่อจัดโครงสร้างแบบจำลองการสืบทอดของคุณ นี่คือบทความที่น่าสนใจเกี่ยวกับ Solidity เกี่ยวกับปัญหาที่เรียกว่าเพชร

Enums

Enums ได้รับการสนับสนุนใน 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 }

ตามที่คุณคาดหวัง (ไม่แตกต่างจากภาษาที่คุ้นเคย) แต่ละค่า enum ถูกกำหนดเป็นค่าจำนวนเต็ม เริ่มต้นด้วย 0 ตามที่ระบุไว้ในเอกสาร Solidity ค่า enum สามารถแปลงเป็นจำนวนเต็มได้ทุกประเภท (เช่น uint, uint16, uint32, ฯลฯ) แต่ไม่อนุญาตให้แปลงโดยนัย ซึ่งหมายความว่าพวกเขาจะต้องถูกโยนอย่างชัดเจน (เช่น uint)

Solidity Docs: Enums Enums บทช่วยสอน

โครงสร้าง

โครงสร้างเป็นอีกวิธีหนึ่ง เช่น enums ในการสร้างประเภทข้อมูลที่กำหนดโดยผู้ใช้ โครงสร้างเป็นที่คุ้นเคยสำหรับ coders พื้นฐาน C/C++ และคนรุ่นเก่าเช่นฉัน ตัวอย่างของ struct จากบรรทัดที่ 17 ของ BoxingOracle.sol:

 //defines a match along with its outcome struct Match { bytes32 id; string name; string participants; uint8 participantCount; uint date; MatchOutcome outcome; int8 winner; }

หมายเหตุถึงโปรแกรมเมอร์ C รุ่นเก่าๆ ทุกคน: Struct “packing” ใน Solidity เป็นเรื่องหนึ่ง แต่ก็มีกฎเกณฑ์และข้อควรระวังบางประการ ไม่จำเป็นต้องทึกทักเอาเองว่ามันทำงานเหมือนกับในภาษา C; ตรวจสอบเอกสารและรับทราบสถานการณ์ของคุณ เพื่อให้แน่ใจว่าการบรรจุจะช่วยคุณในกรณีที่กำหนดหรือไม่

การบรรจุโครงสร้างที่เป็นของแข็ง

เมื่อสร้างแล้ว สามารถระบุโครงสร้างในโค้ดของคุณเป็นประเภทข้อมูลดั้งเดิมได้ ต่อไปนี้คือตัวอย่างไวยากรณ์สำหรับ "อินสแตนซ์" ของประเภทโครงสร้างที่สร้างขึ้นด้านบน:

 Match match = Match(id, "A vs. B", "A|B", 2, block.timestamp, MatchOutcome.Pending, 1);

ประเภทข้อมูลใน Solidity

สิ่งนี้นำเราไปสู่หัวข้อพื้นฐานของประเภทข้อมูลใน Solidity solidity รองรับข้อมูลประเภทใดบ้าง? Solidity ถูกพิมพ์แบบสแตติก และในขณะที่เขียนประเภทข้อมูลนี้จะต้องประกาศและผูกกับตัวแปรอย่างชัดเจน

ประเภทข้อมูลใน Ethereum Solidity

ประเภทข้อมูลความแข็งแกร่ง

บูลีน

รองรับประเภทบูลีนภายใต้ชื่อ บู ลและค่า จริง หรือ เท็จ

ประเภทตัวเลข

รองรับประเภทจำนวนเต็ม ทั้งที่ลงชื่อและไม่ได้ลงชื่อ ตั้งแต่ 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

หมายเหตุ: ในกรณีส่วนใหญ่ วิธีที่ดีที่สุดคือใช้ uint เนื่องจากการลดขนาดของตัวแปร (เช่น เป็น uint32) สามารถเพิ่มต้นทุนค่าน้ำมันได้จริงแทนที่จะลดตามที่คุณคาดหวัง ตามหลักการทั่วไป ใช้ uint เว้นแต่คุณจะแน่ใจว่าคุณมีเหตุผลที่ดีในการทำอย่างอื่น

ประเภทสตริง

ประเภทข้อมูลสตริงใน Solidity เป็นเรื่องตลก คุณอาจได้รับความคิดเห็นที่แตกต่างกันขึ้นอยู่กับว่าคุณคุยกับใคร มีประเภทข้อมูลสตริงใน Solidity นั่นคือข้อเท็จจริง ความคิดเห็นของฉันซึ่งคนส่วนใหญ่มักมีร่วมกันก็คือว่ามันไม่มีฟังก์ชันการทำงานมากนัก การแยกวิเคราะห์สตริง การต่อสาย แทนที่ ตัดแต่ง แม้แต่การนับความยาวของสตริง: ไม่มีสิ่งที่คุณน่าจะคาดหวังจากประเภทสตริง ดังนั้นจึงเป็นความรับผิดชอบของคุณ (ถ้าคุณต้องการ) บางคนใช้ bytes32 แทนสตริง ที่สามารถทำได้เช่นกัน

บทความสนุกๆ เกี่ยวกับ Solidity strings

ความคิดเห็นของฉัน: การเขียนประเภทสตริงของคุณเองและเผยแพร่สำหรับการใช้งานทั่วไปอาจเป็นแบบฝึกหัดที่สนุก

ประเภทที่อยู่

เฉพาะสำหรับ Solidity เท่านั้น เรามีประเภทข้อมูล ที่อยู่ โดยเฉพาะสำหรับกระเป๋าเงิน Ethereum หรือที่อยู่ตามสัญญา เป็นค่า 20 ไบต์โดยเฉพาะสำหรับการจัดเก็บที่อยู่ของขนาดนั้นโดยเฉพาะ นอกจากนี้ยังมีประเภทสมาชิกสำหรับที่อยู่ประเภทนั้นโดยเฉพาะ

 address internal boxingOracleAddr = 0x145ca3e014aaf5dca488057592ee45305d9b3a22;

ที่อยู่ประเภทข้อมูล

วันที่เวลาประเภท

ไม่มีประเภท Date หรือ DateTime ดั้งเดิมใน Solidity ตามที่มีใน JavaScript เป็นต้น (ไม่นะ—เสียงของ Solidity ฟังดูแย่และแย่ลงทุกย่อหน้า!?) วันที่จะถูกระบุโดยกำเนิดเป็นการประทับเวลาของประเภท uint (uint256) โดยทั่วไปจะถูกจัดการเป็นการประทับเวลาสไตล์ Unix ในหน่วยวินาทีแทนที่จะเป็นมิลลิวินาที เนื่องจากเวลาประทับของบล็อกเป็นการประทับเวลาสไตล์ Unix ในกรณีที่คุณพบว่าตัวเองต้องการวันที่ที่มนุษย์สามารถอ่านได้ด้วยเหตุผลหลายประการ มีไลบรารีโอเพ่นซอร์สที่พร้อมให้บริการ คุณอาจสังเกตเห็นว่าฉันเคยใช้ใน BoxingOracle: DateLib.sol OpenZeppelin ยังมียูทิลิตี้วันที่เช่นเดียวกับไลบรารียูทิลิตี้ทั่วไปประเภทอื่น ๆ อีกมากมาย (เราจะไปที่คุณสมบัติ ไลบรารี ของ Solidity ในไม่ช้า)

เคล็ดลับสำหรับ มือโปร: OpenZeppelin เป็นแหล่งที่ดี (แต่แน่นอนว่าไม่ใช่แหล่งเดียวที่ดี) สำหรับทั้งความรู้และโค้ดทั่วไปที่เขียนไว้ล่วงหน้าซึ่งอาจช่วยให้คุณสร้างสัญญาได้

การทำแผนที่

สังเกตว่าบรรทัดที่ 11 ของ BoxingOracle.sol กำหนดสิ่งที่เรียกว่า mapping :

 mapping(bytes32 => uint) matchIdToIndex;

การแมปใน Solidity เป็นชนิดข้อมูลพิเศษสำหรับการค้นหาอย่างรวดเร็ว โดยพื้นฐานแล้วคือตารางค้นหาหรือคล้ายกับ hashtable ซึ่งข้อมูลที่มีอยู่ใน blockchain นั้นเอง (เมื่อมีการกำหนดการทำแผนที่ตามที่อยู่ที่นี่ในฐานะสมาชิกคลาส) ในระหว่างการดำเนินการตามสัญญา เราสามารถเพิ่มข้อมูลลงในแผนที่ได้ เช่นเดียวกับการเพิ่มข้อมูลลงใน hashtable และค้นหาค่าที่เราได้เพิ่มเข้าไปในภายหลัง โปรดสังเกตอีกครั้งว่าในกรณีนี้ ข้อมูลที่เราเพิ่มจะถูกเพิ่มไปยังบล็อคเชน ดังนั้นมันจะยังคงมีอยู่ หากเราเพิ่มลงในแผนที่วันนี้ในนิวยอร์ก หนึ่งสัปดาห์ต่อจากนี้ใครบางคนในอิสตันบูลสามารถอ่านได้

ตัวอย่างการเพิ่มการแมปจากบรรทัดที่ 71 ของ BoxingOracle.sol:

 matchIdToIndex[id] = newIndex+1

ตัวอย่างการอ่านจากการแมป จากบรรทัดที่ 51 ของ BoxingOracle.sol:

 uint index = matchIdToIndex[_matchId];

รายการยังสามารถลบออกจากการแมป ไม่ได้ใช้ในโครงการนี้ แต่จะมีลักษณะดังนี้:

 delete matchIdToIndex[_matchId];

ส่งกลับค่า

ตามที่คุณอาจสังเกตเห็น Solidity อาจมีความคล้ายคลึงกับ Javascript เพียงผิวเผินเล็กน้อย แต่ก็ไม่ได้สืบทอดประเภทและคำจำกัดความของ JavaScript มากนัก ต้องกำหนดรหัสสัญญาในลักษณะที่ค่อนข้างเข้มงวดและจำกัด (และนี่อาจเป็นสิ่งที่ดีเมื่อพิจารณาจากกรณีการใช้งาน) ด้วยเหตุนี้ ให้พิจารณานิยามฟังก์ชันจากบรรทัดที่ 40 ของ BoxingOracle.sol

 function _getMatchIndex(bytes32 _matchId) private view returns (uint) { ... }

เอาล่ะ เรามาทำภาพรวมคร่าวๆ ของสิ่งที่มีอยู่ในนี้กันก่อน function ทำเครื่องหมายว่าเป็นฟังก์ชัน _getMatchIndex คือชื่อฟังก์ชัน (ขีดล่างเป็นแบบแผนที่ระบุสมาชิกส่วนตัว เราจะพูดถึงในภายหลัง) ต้องใช้หนึ่งอาร์กิวเมนต์ ชื่อ _matchId (คราวนี้จะใช้แบบแผนการขีดล่างเพื่อแสดงอาร์กิวเมนต์ของฟังก์ชัน) ของประเภท bytes32 คำหลัก private ทำให้สมาชิกมีความเป็นส่วนตัวจริง ๆ ในขอบเขต view บอกคอมไพเลอร์ว่าฟังก์ชันนี้ไม่ได้แก้ไขข้อมูลใด ๆ บน blockchain และสุดท้าย: ~~~ ผลตอบแทนที่เป็นของแข็ง (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) { ... }

ตรวจสอบข้อส่งคืนในอันนี้! มันส่งกลับหนึ่ง สอง… เจ็ดสิ่งที่แตกต่างกัน โอเค ฟังก์ชันนี้จะคืนค่าสิ่งเหล่านี้เป็นทูเพิล ทำไม? ในระหว่างการพัฒนา คุณมักจะพบว่าตัวเองจำเป็นต้องส่งคืนโครงสร้าง (หากเป็น JavaScript คุณอาจต้องการส่งคืนวัตถุ JSON อาจเป็นไปได้) ในขณะที่เขียนนี้ (แม้ว่าในอนาคตอาจมีการเปลี่ยนแปลง) Solidity ไม่รองรับการส่งคืนโครงสร้างจากฟังก์ชันสาธารณะ ดังนั้นคุณต้องกลับทูเพิลแทน หากคุณเป็น Python คุณอาจจะคุ้นเคยกับ tuples แล้ว หลายภาษาไม่สนับสนุนพวกเขาจริงๆ อย่างน้อยก็ไม่ด้วยวิธีนี้

ดูบรรทัดที่ 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 ค่า ซึ่งตอนนี้เราสามารถใช้ได้ มิฉะนั้น สมมติว่าเราต้องการเพียงหนึ่งหรือสองค่า เราสามารถพูดได้ว่า:

 //declare the variables bytes32 id; uint date; //assign their values (id,,,,date,,) = getMostRecentMatch(false);

ดูสิ่งที่เราทำที่นั่น? เรามีแค่สองตัวที่เราสนใจ ลองดูเครื่องหมายจุลภาคเหล่านั้นทั้งหมด เราต้องนับอย่างระมัดระวัง!

นำเข้า

BoxingOracle.sol บรรทัดที่ 3 และ 4 เป็นการนำเข้า:

 import "./Ownable.sol"; import "./DateLib.sol";

อย่างที่คุณคาดไว้ สิ่งเหล่านี้กำลังนำเข้าคำจำกัดความจากไฟล์โค้ดที่มีอยู่ในโฟลเดอร์โปรเจ็กต์ contract เดียวกันกับ BoxingOracle.sol

ตัวดัดแปลง

ขอให้สังเกตว่าคำจำกัดความของฟังก์ชันมีตัวแก้ไขจำนวนมากแนบอยู่ ประการแรก มีการมองเห็น: ส่วนตัว สาธารณะ ภายในและภายนอก—การมองเห็นฟังก์ชัน

นอกจากนี้ คุณจะเห็นคำหลัก pure และ view สิ่งเหล่านี้บ่งชี้ให้คอมไพเลอร์ทราบว่าฟังก์ชันจะทำการเปลี่ยนแปลงประเภทใด หากมี นี่เป็นสิ่งสำคัญเพราะสิ่งนี้เป็นปัจจัยในต้นทุนก๊าซขั้นสุดท้ายของการใช้งานฟังก์ชัน ดูคำอธิบายที่นี่: Solidity Docs

สุดท้ายนี้ สิ่งที่ฉันต้องการจะพูดถึงจริงๆ คือตัวปรับแต่งที่กำหนดเอง ดูที่บรรทัดที่ 61 ของ BoxingOracle.sol:

 function addMatch(string _name, string _participants, uint8 _participantCount, uint _date) onlyOwner public returns (bytes32) {

โปรดสังเกตตัวแก้ไข onlyOwner ก่อนคีย์เวิร์ด "สาธารณะ" แสดงว่า เฉพาะเจ้าของ สัญญาเท่านั้นที่เรียกวิธีนี้ได้! แม้ว่าจะมีความสำคัญมาก แต่ก็ไม่ใช่คุณลักษณะดั้งเดิมของ Solidity (แม้ว่าอาจจะเป็นในอนาคต) อันที่จริง onlyOwner เป็นตัวอย่างของตัวปรับแต่งที่เราสร้างขึ้นเองและใช้งาน มาดูกันเลย

อันดับแรก ตัวแก้ไขถูกกำหนดในไฟล์ Ownable.sol ซึ่งคุณจะเห็นว่าเรานำเข้าในบรรทัดที่ 3 ของ BoxingOracle.sol:

 import "./Ownable.sol"

โปรดทราบว่าเพื่อใช้ประโยชน์จากตัวแก้ไข เราได้ทำให้ BoxingOracle สืบทอดจาก Ownable ภายใน Ownable.sol ในบรรทัดที่ 25 เราสามารถค้นหาคำจำกัดความของตัวแก้ไขภายในสัญญา "เป็นเจ้าของได้":

 modifier onlyOwner() { require(msg.sender == owner); _; }

(สัญญาที่เป็นเจ้าของได้นี้โดยวิธีการ นำมาจากสัญญาสาธารณะของ OpenZeppelin)

โปรดทราบว่าสิ่งนี้ถูกประกาศเป็นตัวดัดแปลง ซึ่งบ่งชี้ว่าเราสามารถใช้มันตามที่เรามี เพื่อแก้ไขฟังก์ชันได้ โปรดทราบว่าเนื้อของตัวดัดแปลงเป็นคำสั่ง "require" คำสั่ง Require เป็นเหมือนการยืนยัน แต่ไม่ใช่สำหรับการดีบัก หากเงื่อนไขของคำสั่ง require ล้มเหลว ฟังก์ชันจะส่งข้อยกเว้น ดังนั้นเพื่อถอดความข้อความ "ต้องการ" นี้:

 require(msg.sender == owner);

เราสามารถพูดได้ว่ามันหมายถึง:

 if (msg.send != owner) throw an exception;

และที่จริงแล้ว ใน Solidity 0.4.22 ขึ้นไป เราสามารถเพิ่มข้อความแสดงข้อผิดพลาดไปยังคำสั่งที่ต้องการได้:

 require(msg.sender == owner, "Error: this function is callable by the owner of the contract, only");

ในที่สุดในบรรทัดที่ดูอยากรู้อยากเห็น:

 _;

ขีดล่างเป็นแบบย่อสำหรับ "ที่นี่ ดำเนินการเนื้อหาทั้งหมดของฟังก์ชันที่แก้ไข" ดังนั้น คำสั่ง require จะดำเนินการก่อน ตามด้วยฟังก์ชันจริง ดังนั้นจึงเหมือนกับการรอตรรกะบรรทัดนี้ล่วงหน้ากับฟังก์ชันที่แก้ไข

แน่นอน มีหลายสิ่งที่คุณสามารถทำกับตัวปรับแต่งได้ ตรวจสอบเอกสาร: เอกสาร

Solidity Libraries

มีคุณลักษณะทางภาษาของ Solidity ที่เรียกว่า ไลบรารี เรามีตัวอย่างในโครงการของเราที่ DateLib.sol

การใช้งาน Solidity Library!

นี่คือไลบรารีสำหรับการจัดการประเภทวันที่ที่ง่ายขึ้น มันถูกนำเข้าไปยัง BoxingOracle ที่บรรทัด 4:

 import "./DateLib.sol";

และใช้ในบรรทัดที่ 13:

 using DateLib for DateLib.DateTime;

DateLib.DateTime เป็นโครงสร้างที่ส่งออกจากสัญญา DateLib (เปิดเผยในฐานะสมาชิก ดูบรรทัดที่ 4 ของ DateLib.sol) และเรากำลังประกาศที่นี่ว่าเรากำลัง "ใช้" ไลบรารี DateLib สำหรับประเภทข้อมูลบางประเภท ดังนั้นเมธอดและการดำเนินการที่ประกาศไว้ในไลบรารีนั้นจะนำไปใช้กับชนิดข้อมูลที่เราได้กล่าวว่าควร นั่นคือวิธีการใช้ไลบรารีใน Solidity

สำหรับตัวอย่างที่ชัดเจนยิ่งขึ้น ให้ตรวจดูตัวเลขในไลบรารีของ OpenZeppelin เช่น SafeMath สิ่งเหล่านี้สามารถนำไปใช้กับชนิดข้อมูล Solidity ดั้งเดิม (ตัวเลข) (ในขณะที่เราใช้ไลบรารีกับชนิดข้อมูลที่กำหนดเอง) และใช้กันอย่างแพร่หลาย

อินเทอร์เฟซ

อินเทอร์เฟซได้รับการสนับสนุนเช่นเดียวกับในภาษาเชิงวัตถุหลัก อินเทอร์เฟซใน Solidity ถูกกำหนดให้เป็นสัญญา แต่เนื้อหาฟังก์ชันจะถูกละเว้นสำหรับฟังก์ชัน สำหรับตัวอย่างการกำหนดอินเทอร์เฟซ โปรดดูที่ OracleInterface.sol ในตัวอย่างนี้ อินเทอร์เฟซถูกใช้เป็นแบบสแตนด์อินสำหรับสัญญา oracle ซึ่งมีเนื้อหาอยู่ในสัญญาแยกต่างหากโดยมีที่อยู่แยกต่างหาก

อนุสัญญาการตั้งชื่อ

แน่นอนว่าการตั้งชื่อแบบแผนไม่ใช่กฎสากล ในฐานะโปรแกรมเมอร์ เรารู้ว่าเรามีอิสระที่จะปฏิบัติตามข้อตกลงในการเขียนโปรแกรมและการตั้งชื่อที่ดึงดูดใจเรา ในทางกลับกัน เราต้องการให้ผู้อื่นรู้สึกสบายใจในการอ่านและทำงานกับโค้ดของเรา ดังนั้นจึงควรกำหนดมาตรฐานในระดับหนึ่ง

ภาพรวมโครงการ

ตอนนี้เราได้ดูฟีเจอร์ภาษาทั่วไปบางอย่างที่มีอยู่ในไฟล์โค้ดที่เป็นปัญหาแล้ว เราสามารถเริ่มพิจารณาโค้ดที่เจาะจงมากขึ้นสำหรับโปรเจ็กต์นี้

จึงขอชี้แจงวัตถุประสงค์ของโครงการนี้อีกครั้ง วัตถุประสงค์ของโครงการนี้คือเพื่อให้มีการสาธิตแบบกึ่งเสมือนจริง (หรือเสมือนจริง) และตัวอย่างของสัญญาอัจฉริยะที่ใช้พยากรณ์ หัวใจของสิ่งนี้เป็นเพียงสัญญาที่เรียกแยกสัญญาอีกฉบับหนึ่ง

กรณีธุรกิจของตัวอย่างสามารถระบุได้ดังนี้:

  • ผู้ใช้ต้องการเดิมพันขนาดต่างๆ ในการแข่งขันชกมวย จ่ายเงิน (อีเธอร์) สำหรับการเดิมพันและเก็บเงินรางวัลของพวกเขาเมื่อใดและหากพวกเขาชนะ
  • ผู้ใช้ทำการเดิมพันเหล่านี้ผ่านสัญญาอัจฉริยะ (ในกรณีการใช้งานจริง นี่จะเป็น DApp เต็มรูปแบบที่มีส่วนหน้าของ web3 แต่เรากำลังตรวจสอบเฉพาะด้านสัญญาเท่านั้น)
  • สัญญาอัจฉริยะที่แยกต่างหาก—ออราเคิล—ได้รับการดูแลโดยบุคคลที่สาม หน้าที่ของมันคือการรักษารายการการแข่งขันชกมวยกับสถานะปัจจุบัน (อยู่ระหว่างดำเนินการ อยู่ระหว่างดำเนินการ เสร็จสิ้น ฯลฯ) และหากทำเสร็จแล้ว ผู้ชนะ
  • สัญญาหลักได้รับรายการการแข่งขันที่รอดำเนินการจาก oracle และนำเสนอให้กับผู้ใช้ในรูปแบบการแข่งขันที่ "เดิมพันได้"
  • สัญญาหลักยอมรับการเดิมพันจนถึงเริ่มการแข่งขัน
  • หลังจากตัดสินการแข่งขันแล้ว สัญญาหลักจะแบ่งเงินที่ชนะและแพ้ออกตามอัลกอริธึมง่ายๆ ตัดทอนให้ตัวเอง และจ่ายเงินรางวัลที่ได้รับตามคำขอ (ผู้แพ้จะเสียเดิมพันทั้งหมด)

กฎการเดิมพัน:

  • มีการกำหนดเดิมพันขั้นต่ำ (กำหนดใน wei)
  • ไม่มีการเดิมพันสูงสุด ผู้ใช้สามารถเดิมพันจำนวนใด ๆ ที่พวกเขาชอบเหนือขั้นต่ำ
  • ผู้ใช้สามารถวางเดิมพันได้จนถึงเวลาที่การแข่งขัน "อยู่ในระหว่างดำเนินการ"

อัลกอริทึมสำหรับการแบ่งเงินรางวัล:

  • เดิมพันทั้งหมดที่ได้รับจะถูกวางลงใน “หม้อ”
  • เปอร์เซ็นต์เล็กน้อยจะถูกลบออกจากหม้อสำหรับบ้าน
  • ผู้ชนะแต่ละคนจะได้รับรางวัลเป็นสัดส่วนของเงินกองกลาง ซึ่งเป็นสัดส่วนโดยตรงกับขนาดเดิมพันของพวกเขา
  • เงินรางวัลจะถูกคำนวณทันทีที่ผู้ใช้คนแรกร้องขอผลการแข่งขัน หลังจากที่ตัดสินการแข่งขันแล้ว
  • ผู้ชนะจะได้รับตามคำขอจากผู้ใช้
  • ในกรณีที่เสมอกัน ไม่มีใครชนะ—ทุกคนจะได้รับเงินเดิมพันคืน และเจ้ามือจะไม่ถูกตัดออก

BoxingOracle: สัญญาออราเคิล

ฟังก์ชั่นหลักที่มีให้

oracle มีสองอินเทอร์เฟซ คุณสามารถพูดได้ว่า: หนึ่งนำเสนอต่อ "เจ้าของ" และผู้รักษาสัญญา และอีกอันนำเสนอต่อสาธารณชนทั่วไป นั่นคือสัญญาที่กินคำพยากรณ์ ผู้ดูแลมีฟังก์ชันสำหรับป้อนข้อมูลลงในสัญญา โดยหลักๆ แล้วจะนำข้อมูลจากโลกภายนอกมาใส่ในบล็อกเชน แก่สาธารณะ มีการเข้าถึงข้อมูลดังกล่าวแบบอ่านอย่างเดียว สิ่งสำคัญที่ควรทราบคือ สัญญานั้นจำกัดผู้ที่ไม่ใช่เจ้าของจากการแก้ไขข้อมูลใดๆ แต่การเข้าถึงข้อมูลนั้นแบบอ่านอย่างเดียวจะได้รับแบบสาธารณะโดยไม่มีข้อจำกัด

ถึงผู้ใช้:

  • รายการการแข่งขันทั้งหมด
  • รายการการแข่งขันที่รอดำเนินการ
  • รับรายละเอียดของการแข่งขันที่เฉพาะเจาะจง
  • รับสถานะและผลของการแข่งขันที่เฉพาะเจาะจง

ถึงเจ้าของ:

  • เข้าแข่งขัน
  • เปลี่ยนสถานะของการแข่งขัน
  • กำหนดผลการแข่งขัน

ภาพประกอบขององค์ประกอบการเข้าถึงของผู้ใช้และเจ้าของ

เรื่องราวของผู้ใช้:

  • มีการประกาศและยืนยันการแข่งขันชกมวยใหม่ในวันที่ 9 พฤษภาคม
  • ฉัน ผู้ดูแลสัญญา (บางทีฉันอาจเป็นเครือข่ายกีฬาที่มีชื่อเสียงหรือร้านใหม่) เพิ่มการจับคู่ที่จะเกิดขึ้นกับข้อมูลของ oracle บนบล็อกเชน โดยมีสถานะ "รอดำเนินการ" ขณะนี้ใครก็ตามหรือสัญญาใด ๆ สามารถสอบถามและใช้ข้อมูลนี้ได้ตามต้องการ
  • เมื่อการแข่งขันเริ่มขึ้น ฉันตั้งค่าสถานะของการแข่งขันนั้นเป็น "กำลังดำเนินการ"
  • เมื่อการแข่งขันสิ้นสุดลง ฉันตั้งค่าสถานะของการแข่งขันเป็น "เสร็จสิ้น" และแก้ไขข้อมูลการแข่งขันเพื่อแสดงผู้ชนะ

Oracle Code Review

บทวิจารณ์นี้อ้างอิงจาก BoxingOracle.sol ทั้งหมด; หมายเลขบรรทัดอ้างอิงไฟล์นั้น

ในบรรทัดที่ 10 และ 11 เราประกาศพื้นที่จัดเก็บสำหรับการแข่งขัน:

 Match[] matches; mapping(bytes32 => uint) matchIdToIndex;

matches เป็นเพียงอาร์เรย์ง่าย ๆ สำหรับจัดเก็บอินสแตนซ์การจับคู่ และการแมปเป็นเพียงสิ่งอำนวยความสะดวกสำหรับการแมป ID การจับคู่ที่ไม่ซ้ำกัน (ค่าไบต์32) กับดัชนีในอาร์เรย์ เพื่อที่ว่าถ้ามีคนส่ง 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 ของการแข่งขันทั้งหมดที่มีสถานะปัจจุบันคือ "pending"
  • ฟังก์ชัน getAllMatches ส่งคืนรายการ ID ของการแข่งขันทั้งหมด
  • ฟังก์ชัน getMatch ส่งคืนรายละเอียดทั้งหมดของการจับคู่ครั้งเดียว ซึ่งระบุโดย ID

บรรทัดที่ 193-204 ประกาศฟังก์ชันที่ใช้สำหรับการทดสอบ การดีบัก และการวินิจฉัยเป็นหลัก

  • ฟังก์ชั่น testConnection เป็นเพียงการทดสอบว่าเราสามารถเรียกสัญญาได้
  • ฟังก์ชัน getAddress ส่งคืนที่อยู่ของสัญญานี้
  • ฟังก์ชัน addTestData เพิ่มชุดทดสอบที่ตรงกันในรายการการแข่งขัน

อย่าลังเลที่จะสำรวจโค้ดเล็กน้อยก่อนที่จะไปยังขั้นตอนถัดไป ฉันแนะนำให้เรียกใช้ oracle contract อีกครั้งในโหมดดีบัก (ตามที่อธิบายไว้ในส่วนที่ 1 ของซีรีส์นี้) เรียกใช้ฟังก์ชันต่างๆ และตรวจสอบผลลัพธ์

BoxingBets: สัญญาของลูกค้า

สิ่งสำคัญคือต้องกำหนดสิ่งที่สัญญาของลูกค้า (สัญญาการพนัน) รับผิดชอบและสิ่งที่ไม่รับผิดชอบ สัญญาของลูกค้าจะ ไม่ รับผิดชอบต่อการรักษารายการการแข่งขันชกมวยจริงหรือการประกาศผลการแข่งขัน เรา “เชื่อ” (ใช่ ฉันรู้ มีคำที่ละเอียดอ่อน—เอ่อ—เราจะพูดถึงเรื่องนี้ในตอนที่ 3) คำพยากรณ์สำหรับบริการนั้น สัญญาของลูกค้ามีหน้าที่รับเดิมพัน รับผิดชอบอัลกอริทึมที่แบ่งเงินรางวัลและโอนไปยังบัญชีของผู้ชนะตามผลของการแข่งขัน (ตามที่ได้รับจาก oracle)

นอกจากนี้ทุกอย่างเป็นแบบดึงและไม่มีเหตุการณ์หรือการผลักดัน สัญญาดึงข้อมูลจาก oracle สัญญาดึงผลลัพธ์ของการแข่งขันจาก oracle (ตามคำขอของผู้ใช้) และสัญญาจะคำนวณเงินรางวัลและโอนให้ตามคำขอของผู้ใช้

ฟังก์ชั่นหลักที่มีให้

  • แสดงรายการการแข่งขันที่รอดำเนินการทั้งหมด
  • รับรายละเอียดของการแข่งขันที่เฉพาะเจาะจง
  • รับสถานะและผลของการแข่งขันที่เฉพาะเจาะจง
  • วางเดิมพัน
  • ขอ/รับเงินรางวัล

การตรวจสอบรหัสลูกค้า

บทวิจารณ์นี้อ้างอิงจาก BoxingBets.sol ทั้งหมด; หมายเลขบรรทัดอ้างอิงไฟล์นั้น

บรรทัดที่ 12 และ 13 ซึ่งเป็นโค้ดบรรทัดแรกในสัญญา กำหนดการจับคู่ซึ่งเราจะจัดเก็บข้อมูลสัญญาของเรา

บรรทัดที่ 12 แมปที่อยู่ของผู้ใช้กับรายการ ID นี่คือการจับคู่ผู้ใช้กับรายการ ID ของการเดิมพันที่เป็นของผู้ใช้ ดังนั้นสำหรับที่อยู่ผู้ใช้ใดๆ เราสามารถรับรายการเดิมพันทั้งหมดที่ผู้ใช้รายนั้นทำได้อย่างรวดเร็ว

 mapping(address => bytes32[]) private userToBets;

Line 13 จับคู่ ID เฉพาะของการแข่งขันกับรายการอินสแตนซ์การเดิมพัน ด้วยสิ่งนี้ เราสามารถรับรายการเดิมพันทั้งหมดสำหรับนัดนั้นสำหรับการแข่งขันที่กำหนด

 mapping(bytes32 => Bet[]) private matchToBets;

บรรทัดที่ 17 และ 18 เกี่ยวข้องกับการเชื่อมต่อกับ oracle ของเรา อันดับแรก ในตัวแปร boxingOracleAddr เราเก็บที่อยู่ของสัญญา oracle (ตั้งค่าเป็นศูนย์โดยค่าเริ่มต้น) เราสามารถฮาร์ดโค้ดที่อยู่ของ oracle ได้ แต่หลังจากนั้นเราจะไม่สามารถเปลี่ยนแปลงได้ (การไม่สามารถเปลี่ยนที่อยู่ของ oracle อาจเป็นเรื่องดีหรือไม่ดี—เราสามารถพูดถึงเรื่องนี้ได้ในส่วนที่ 3) บรรทัดถัดไปจะสร้างอินสแตนซ์ของอินเทอร์เฟซของ oracle (ซึ่งกำหนดไว้ใน 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 setOracleAddress มีตัวแก้ไข onlyOwner เนื่องจากมีเพียงเจ้าของสัญญาเท่านั้นที่สามารถเปลี่ยน oracle ให้กับ oracle อื่นได้ (อาจ ไม่ใช่ ความคิดที่ดี แต่เราจะอธิบายให้ละเอียดในส่วนที่ 3) ในทางกลับกัน ฟังก์ชัน getOracleAddress สามารถเรียกแบบสาธารณะได้ ใครๆ ก็เห็นว่าใช้ Oracle อะไรอยู่

 function setOracleAddress(address _oracleAddress) external onlyOwner returns (bool) {... function getOracleAddress() external view returns (address) { ....

ในบรรทัดที่ 72 และ 79 เรามีฟังก์ชัน getBettableMatches และ getMatch ตามลำดับ โปรดทราบว่าสิ่งเหล่านี้เป็นเพียงการส่งต่อการโทรไปยัง oracle และส่งคืนผลลัพธ์

 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 เราจะโอนจำนวนนั้นไปสู่ความเป็นเจ้าของของ oracle นำความเป็นเจ้าของจำนวนนั้นออกจากผู้ใช้และเข้าสู่การครอบครองของสัญญา:

 address(this).transfer(msg.value);

สุดท้าย ในบรรทัดที่ 136 เรามีฟังก์ชันตัวช่วยทดสอบ/แก้จุดบกพร่องที่จะช่วยให้เราทราบว่าสัญญาเชื่อมต่อกับ oracle ที่ถูกต้องหรือไม่:

 function testOracleConnection() public view returns (bool) { return boxingOracle.testConnection(); }

ห่อ

และนี่คือเท่าที่ตัวอย่างนี้ดำเนินไป เพียงแค่ยอมรับการเดิมพัน ฟังก์ชันสำหรับการแบ่งเงินรางวัลและการจ่ายเงิน รวมถึงตรรกะอื่นๆ ถูกละไว้โดยเจตนาเพื่อให้ตัวอย่างเรียบง่ายเพียงพอสำหรับจุดประสงค์ของเรา ซึ่งก็เพียงเพื่อสาธิตการใช้ oracle กับสัญญา ตรรกะที่สมบูรณ์และซับซ้อนกว่านั้นมีอยู่ในโครงการอื่นในปัจจุบัน ซึ่งเป็นส่วนเสริมของตัวอย่างนี้และยังอยู่ในระหว่างการพัฒนา

ดังนั้นตอนนี้ เรามีความเข้าใจที่ดีขึ้นเกี่ยวกับ codebase และได้ใช้เป็นพาหนะและจุดเริ่มต้นเพื่อหารือเกี่ยวกับคุณสมบัติภาษาบางอย่างที่ Solidity นำเสนอ จุดประสงค์หลักของชุดข้อมูลสามส่วนนี้คือเพื่อสาธิตและหารือเกี่ยวกับการใช้สัญญากับออราเคิล จุดประสงค์ของส่วนนี้คือเพื่อทำความเข้าใจโค้ดเฉพาะนี้ให้ดีขึ้นอีกนิด และใช้มันเป็นจุดเริ่มต้นในการทำความเข้าใจคุณลักษณะบางอย่างของ Solidity และการพัฒนาสัญญาอัจฉริยะ จุดประสงค์ของส่วนที่สามและส่วนสุดท้ายคือเพื่อหารือเกี่ยวกับกลยุทธ์และปรัชญาของการใช้งาน oracle และวิธีการที่เข้ากับแนวคิดในรูปแบบสัญญาอัจฉริยะ

ขั้นตอนเพิ่มเติมเพิ่มเติม

ฉันขอแนะนำให้ผู้อ่านที่ต้องการเรียนรู้เพิ่มเติม นำโค้ดนี้ไปลองใช้ดู ใช้คุณสมบัติใหม่ แก้ไขข้อบกพร่องใด ๆ ใช้คุณสมบัติที่ยังไม่ได้ใช้งาน (เช่น อินเทอร์เฟซการชำระเงิน) ทดสอบการเรียกใช้ฟังก์ชัน แก้ไขและทดสอบใหม่เพื่อดูว่าเกิดอะไรขึ้น เพิ่มส่วนหน้าของ web3 เพิ่มระบบอำนวยความสะดวกในการลบการแข่งขันหรือแก้ไขผลการแข่งขัน (ในกรณีที่เกิดข้อผิดพลาด) แล้วการแข่งขันที่ถูกยกเลิกล่ะ? ใช้ออราเคิลที่สอง แน่นอน สัญญามีอิสระที่จะใช้ oracles ได้มากเท่าที่ต้องการ แต่จะเกิดปัญหาอะไรขึ้น? ขอให้สนุกกับมัน นั่นเป็นวิธีที่ดีในการเรียนรู้ และเมื่อคุณทำแบบนั้น (และรับความเพลิดเพลินจากมัน) คุณจะแน่ใจว่าจะเก็บสิ่งที่คุณได้เรียนรู้มากขึ้น

ตัวอย่างรายการสิ่งที่ควรลองอย่างไม่ครอบคลุม:

  • เรียกใช้ทั้งสัญญาและ oracle ใน testnet ในพื้นที่ (ในทรัฟเฟิลตามที่อธิบายไว้ในส่วนที่ 1) และเรียกใช้ฟังก์ชันที่เรียกได้ทั้งหมดและฟังก์ชันทดสอบทั้งหมด
  • เพิ่มฟังก์ชันสำหรับการคำนวณเงินรางวัลและการจ่ายเงินเมื่อจบการแข่งขัน
  • เพิ่มฟังก์ชันการคืนเงินเดิมพันทั้งหมดในกรณีที่เสมอกัน
  • เพิ่มคุณสมบัติเพื่อขอคืนเงินหรือยกเลิกการเดิมพัน ก่อนการแข่งขันจะเริ่มขึ้น
  • เพิ่มคุณสมบัติเพื่อให้สามารถยกเลิกการแข่งขันได้ในบางครั้ง (ในกรณีนี้ทุกคนจะต้องได้รับเงินคืน)
  • ใช้คุณสมบัติเพื่อรับประกันว่า oracle ที่มีอยู่เมื่อผู้ใช้วางเดิมพันเป็น oracle เดียวกันกับที่ใช้ในการพิจารณาผลของการแข่งขันนั้น
  • ใช้ oracle อื่น (ที่สอง) ที่มีคุณสมบัติแตกต่างกันหรืออาจใช้สำหรับกีฬาอื่นที่ไม่ใช่มวย (โปรดทราบว่าจำนวนผู้เข้าร่วมและรายชื่ออนุญาตให้มีกีฬาประเภทต่างๆ ดังนั้นเราจึงไม่ได้จำกัดแค่การชกมวยเท่านั้น) .
  • ใช้ getMostRecentMatch เพื่อให้ส่งกลับการจับคู่ที่เพิ่มล่าสุด หรือการจับคู่ที่ใกล้เคียงที่สุดกับวันที่ปัจจุบันในแง่ของเวลาที่จะเกิดขึ้น
  • ใช้การจัดการข้อยกเว้น

เมื่อคุณคุ้นเคยกับกลไกของความสัมพันธ์ระหว่างสัญญาและ oracle แล้ว ในตอนที่ 3 ของชุดข้อมูลสามส่วนนี้ เราจะพูดถึงประเด็นเชิงกลยุทธ์ การออกแบบ และปรัชญาบางประเด็นที่หยิบยกมาจากตัวอย่างนี้