สัญญา Ethereum Oracle: คุณสมบัติรหัสความแข็งแกร่ง
เผยแพร่แล้ว: 2022-03-11ในส่วนแรกของสามส่วนนี้ เราได้อ่านบทช่วยสอนเล็กๆ น้อยๆ ที่ให้คู่สัญญากับออราเคิลง่ายๆ แก่เรา มีการอธิบายกลไกและกระบวนการของการตั้งค่า (ด้วยทรัฟเฟิล) การคอมไพล์โค้ด การปรับใช้กับเครือข่ายทดสอบ การรัน และการดีบั๊ก อย่างไรก็ตาม รายละเอียดจำนวนมากของรหัสถูกกลบด้วยลักษณะหยักด้วยมือ ตามที่ได้สัญญาไว้ เราจะมาดูคุณสมบัติทางภาษาบางอย่างซึ่งมีความพิเศษเฉพาะสำหรับการพัฒนา Solidity smart contract และมีความพิเศษเฉพาะในสถานการณ์ของสัญญา-oracle โดยเฉพาะ แม้ว่าเราจะดูไม่ได้อย่างละเอียดถี่ถ้วน (ฉันจะฝากไว้ให้คุณในการศึกษาต่อ ถ้าคุณต้องการ) เราจะพยายามใช้คุณลักษณะที่โดดเด่นที่สุด น่าสนใจที่สุด และสำคัญที่สุดของโค้ด
เพื่อความสะดวกในการดำเนินการนี้ เราขอแนะนำให้คุณเปิดเวอร์ชันของโปรเจ็กต์ของคุณเอง (หากมี) หรือมีโค้ดสำหรับใช้อ้างอิง
รหัสเต็ม ณ จุดนี้สามารถพบได้ที่นี่: https://github.com/jrkosinski/oracle-example/tree/part2-step1
Ethereum และความแข็งแกร่ง
Solidity ไม่ได้เป็นเพียงภาษาเดียวสำหรับการพัฒนาสัญญาอัจฉริยะ แต่ฉันคิดว่ามันปลอดภัยพอที่จะบอกว่าเป็นภาษาที่ใช้กันทั่วไปและเป็นที่นิยมมากที่สุดสำหรับ Ethereum smart contract แน่นอนว่าเป็นผู้ที่ได้รับการสนับสนุนและข้อมูลที่ได้รับความนิยมมากที่สุดในขณะที่เขียนบทความนี้
ความแข็งแกร่งเป็นแบบเชิงวัตถุและทัวริงสมบูรณ์ ที่กล่าวว่า คุณจะตระหนักถึงข้อจำกัดในตัวของมัน (และโดยเจตนาอย่างเต็มที่) อย่างรวดเร็ว ซึ่งทำให้การเขียนโปรแกรมสัญญาอัจฉริยะรู้สึกแตกต่างจากการแฮ็คทั่วไป
เวอร์ชั่น 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 ถูกพิมพ์แบบสแตติก และในขณะที่เขียนประเภทข้อมูลนี้จะต้องประกาศและผูกกับตัวแปรอย่างชัดเจน
ประเภทข้อมูลความแข็งแกร่ง
บูลีน
รองรับประเภทบูลีนภายใต้ชื่อ บู ลและค่า จริง หรือ เท็จ
ประเภทตัวเลข
รองรับประเภทจำนวนเต็ม ทั้งที่ลงชื่อและไม่ได้ลงชื่อ ตั้งแต่ 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
นี่คือไลบรารีสำหรับการจัดการประเภทวันที่ที่ง่ายขึ้น มันถูกนำเข้าไปยัง 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 ของชุดข้อมูลสามส่วนนี้ เราจะพูดถึงประเด็นเชิงกลยุทธ์ การออกแบบ และปรัชญาบางประเด็นที่หยิบยกมาจากตัวอย่างนี้