แนวทางปฏิบัติที่ดีที่สุดของ Solidity สำหรับ Smart Contract Security

บล็อก 1NewsDevelopersEnterpriseBlockchain ExplainedEvents and ConferencesPressจดหมายข่าว

Contents

สมัครรับจดหมายข่าวของเรา.

ที่อยู่อีเมล

เราเคารพความเป็นส่วนตัวของคุณ

หน้าแรกบล็อกการพัฒนา Blockchain

แนวทางปฏิบัติที่ดีที่สุดของ Solidity สำหรับ Smart Contract Security

จากการตรวจสอบไปจนถึงการพิจารณาการประทับเวลานี่คือเคล็ดลับสำหรับมืออาชีพเพื่อให้แน่ใจว่าสัญญาอัจฉริยะ Ethereum ของคุณได้รับการเสริม by ConsenSys สิงหาคม 21, 2020 โพสต์เมื่อสิงหาคม 21, 2020

ฮีโร่แนวทางปฏิบัติที่ดีที่สุดของความแข็งแกร่ง

โดย ConsenSys Diligence ทีมผู้เชี่ยวชาญด้านความปลอดภัยบล็อกเชนของเรา.

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

เอาล่ะมาเริ่มกันเลย.

ใช้ assert (), require (), revert () อย่างถูกต้อง

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

 ยืนยัน ควรใช้ฟังก์ชันเพื่อทดสอบข้อผิดพลาดภายในเท่านั้นและเพื่อตรวจสอบค่าคงที่.

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

การปฏิบัติตามกระบวนทัศน์นี้ช่วยให้เครื่องมือวิเคราะห์อย่างเป็นทางการสามารถตรวจสอบได้ว่าไม่สามารถเข้าถึง opcode ที่ไม่ถูกต้องได้นั่นหมายความว่าจะไม่มีการละเมิดค่าคงที่ในรหัสและรหัสนั้นได้รับการยืนยันอย่างเป็นทางการ.

ความแข็งแกร่งของ pragma ^ 0.5.0; สัญญา Sharer {function sendHalf (แอดเดรสเจ้าหนี้) ผลตอบแทนเจ้าหนี้สาธารณะ (ยอดคงเหลือ uint) {ต้องใช้ (msg.value% 2 == 0, "จำเป็นต้องมีค่าเท่ากัน."); // Require () สามารถมีสตริงข้อความเสริม uint balanceBeforeTransfer = address (this) .balance; (ความสำเร็จบูล) = addr.call.value (msg.value / 2) (""); ต้องการ (ความสำเร็จ); // เนื่องจากเราคืนเงินหากการโอนล้มเหลวน่าจะมี // ไม่มีทางที่เราจะยังคงมีเงินอยู่ครึ่งหนึ่ง ยืนยัน (ที่อยู่ (นี้) .balance == balanceBeforeTransfer – msg.value / 2); // ใช้สำหรับข้อผิดพลาดภายในในการตรวจสอบที่อยู่ผู้ส่งคืน (นี้) .balance; }} รหัสภาษา: JavaScript (javascript)


ดู SWC-110 & SWC-123

ใช้ตัวปรับแต่งสำหรับการตรวจสอบเท่านั้น

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

Contract Registry {เจ้าของที่อยู่; ฟังก์ชัน isVoter (ที่อยู่ _addr) ผลตอบแทนภายนอก (บูล) {// Code}} สัญญาการเลือกตั้ง {Registry Registry; ตัวแก้ไข isEligible (ที่อยู่ _addr) {ต้องใช้ (Registry.isVoter (_addr)); _; } function vote () isEligible (msg.sender) public {// Code}} Code language: JavaScript (javascript)

ในกรณีนี้สัญญา Registry สามารถทำการโจมตี reentracy ได้โดยเรียก Election.vote () ภายใน isVoter ().

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

ระวังการปัดเศษด้วยการหารจำนวนเต็ม

การหารจำนวนเต็มทั้งหมดปัดเศษลงเป็นจำนวนเต็มที่ใกล้เคียงที่สุด หากคุณต้องการความแม่นยำมากขึ้นให้พิจารณาใช้ตัวคูณหรือเก็บทั้งตัวเศษและตัวส่วน.

(ในอนาคต Solidity จะมี จุดคงที่ พิมพ์ซึ่งจะทำให้ง่ายขึ้น)

// uint ไม่ดี x = 5/2; // ผลลัพธ์คือ 2 หารจำนวนเต็มทั้งหมดจะปัดเศษลงเป็นภาษา integerCode ที่ใกล้ที่สุด: JavaScript (javascript)

การใช้ตัวคูณป้องกันการปัดเศษตัวคูณนี้จะต้องถูกนำมาพิจารณาเมื่อทำงานกับ x ในอนาคต:

// ตัวคูณ uint ดี = 10; uint x = (5 * ตัวคูณ) / 2; รหัสภาษา: JavaScript (javascript)

การจัดเก็บตัวเศษและตัวส่วนหมายความว่าคุณสามารถคำนวณผลลัพธ์ของตัวเศษ / ตัวส่วนนอกโซ่ได้:

// เลข uint ดี = 5; ตัวหาร uint = 2; ภาษารหัส: JavaScript (javascript)

ระวังการแลกเปลี่ยนระหว่าง สัญญานามธรรม และ อินเทอร์เฟซ

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

ฟังก์ชันทางเลือก

ทำให้ฟังก์ชันทางเลือกเป็นเรื่องง่าย

ฟังก์ชันทางเลือก จะถูกเรียกเมื่อสัญญาถูกส่งข้อความโดยไม่มีอาร์กิวเมนต์ (หรือเมื่อไม่มีฟังก์ชันที่ตรงกัน) และสามารถเข้าถึงก๊าซ 2,300 ได้เมื่อเรียกจาก. ส่ง () หรือ. โอน () หากคุณต้องการรับ Ether จาก. ส่ง () หรือ. โอน () สิ่งที่คุณทำได้มากที่สุดในฟังก์ชันทางเลือกคือบันทึกเหตุการณ์ ใช้ฟังก์ชันที่เหมาะสมหากจำเป็นต้องมีการคำนวณก๊าซมากขึ้น.

// ฟังก์ชันไม่ดี () เจ้าหนี้ {ยอดคงเหลือ [msg.sender] + = msg.value; } // เงินฝากฟังก์ชันดี () เจ้าหนี้ภายนอก {ยอดคงเหลือ [msg.sender] + = msg.value; } function () เจ้าหนี้ {ต้องใช้ (msg.data.length == 0); ปล่อย LogDepositReceived (msg.sender); } รหัสภาษา: JavaScript (javascript)

ตรวจสอบความยาวของข้อมูลในฟังก์ชันทางเลือก

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

// ฟังก์ชันไม่ดี () payable {emit LogDepositReceived (msg.sender); } // ฟังก์ชันดี () เจ้าหนี้ {ต้องใช้ (msg.data.length == 0); ปล่อย LogDepositReceived (msg.sender); } รหัสภาษา: JavaScript (javascript)

ทำเครื่องหมายฟังก์ชัน payable และตัวแปรสถานะอย่างชัดเจน

เริ่มจาก Solidity 0.4.0 ทุกฟังก์ชั่นที่รับอีเธอร์จะต้องใช้ตัวปรับเปลี่ยนเจ้าหนี้มิฉะนั้นหากธุรกรรมมีค่า msg.value > 0 จะเปลี่ยนกลับ (ยกเว้นเมื่อถูกบังคับ).

บันทึก: สิ่งที่อาจไม่ชัดเจน: ตัวปรับค่าใช้จ่ายจะใช้กับการโทรจากสัญญาภายนอกเท่านั้น ถ้าฉันเรียกใช้ฟังก์ชันที่ไม่สามารถจ่ายได้ในฟังก์ชัน payable ในสัญญาเดียวกันฟังก์ชันที่ไม่สามารถจ่ายได้จะไม่ล้มเหลวแม้ว่าจะยังคงตั้งค่า msg.value อยู่ก็ตาม.

ทำเครื่องหมายการมองเห็นอย่างชัดเจนในฟังก์ชันและตัวแปรสถานะ

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

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

// ไม่ดี x; // ค่าเริ่มต้นเป็นค่าภายในสำหรับตัวแปรสถานะ แต่ควรสร้างฟังก์ชันที่ชัดเจน buy () {// ค่าเริ่มต้นคือ public // รหัสสาธารณะ} // uint ส่วนตัว y; function buy () ภายนอก {// เรียกได้เฉพาะภายนอกหรือใช้ this.buy ()} function utility () public {// เรียกได้จากภายนอกเช่นเดียวกับภายใน: การเปลี่ยนรหัสนี้ต้องใช้ทั้งสองกรณี } function internalAction () internal {// internal code} ภาษารหัส: PHP (php)

ดู SWC-100 และ SWC-108

ล็อค pragmas เป็นเวอร์ชันคอมไพเลอร์เฉพาะ

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

// ความแข็งแกร่งของ pragma ไม่ดี ^ 0.4.4; // ความแข็งแกร่งของ pragma ที่ดี 0.4.4; ภาษารหัส: JavaScript (javascript)

หมายเหตุ: เวอร์ชัน pragma แบบลอย (เช่น. ^ 0.4.25) จะคอมไพล์แบบละเอียดด้วย 0.4.26-nightly.2018.9.25 อย่างไรก็ตามไม่ควรใช้การสร้างทุกคืนเพื่อคอมไพล์โค้ดสำหรับการผลิต.

คำเตือน: คำสั่ง Pragma สามารถปล่อยให้ลอยได้เมื่อสัญญามีไว้สำหรับการบริโภคโดยนักพัฒนารายอื่นเช่นในกรณีที่มีสัญญาในไลบรารีหรือแพ็คเกจ EthPM มิฉะนั้นผู้พัฒนาจะต้องอัปเดต pragma ด้วยตนเองเพื่อรวบรวมในเครื่อง.

ดู SWC-103

ใช้เหตุการณ์เพื่อตรวจสอบกิจกรรมของสัญญา

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

สัญญาการกุศล {การทำแผนที่ (ที่อยู่ => uint) ยอดคงเหลือ; ฟังก์ชั่นบริจาค () เจ้าหนี้สาธารณะ {ยอดคงเหลือ [msg.sender] + = msg.value; }} เกมที่ทำสัญญา {function buyCoins () payable public {// 5% จะมอบให้กับองค์กรการกุศลเพื่อการกุศล donate.value (msg.value / 20) (); }} รหัสภาษา: JavaScript (javascript)

ที่นี่สัญญาเกมจะโทรภายในไปที่ Charity.donate () ธุรกรรมนี้จะไม่ปรากฏในรายการธุรกรรมภายนอกขององค์กรการกุศล แต่จะปรากฏเฉพาะในธุรกรรมภายในเท่านั้น.

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

สัญญาการกุศล {// กำหนดเหตุการณ์เหตุการณ์ LogDonate (uint _amount); การทำแผนที่ (ที่อยู่ => uint) ยอดคงเหลือ; ฟังก์ชั่นบริจาค () เจ้าหนี้สาธารณะ {ยอดคงเหลือ [msg.sender] + = msg.value; // ปล่อยเหตุการณ์ปล่อย LogDonate (msg.value); }} เกมที่ทำสัญญา {function buyCoins () payable public {// 5% จะมอบให้กับองค์กรการกุศลเพื่อการกุศล donate.value (msg.value / 20) (); }} รหัสภาษา: JavaScript (javascript)

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

หมายเหตุ: ชอบโครงสร้าง Solidity ที่ใหม่กว่า. ชอบโครงสร้าง / นามแฝงเช่นการทำลายตัวเอง (มากกว่าการฆ่าตัวตาย) และ keccak256 (มากกว่า sha3) รูปแบบเช่นต้องใช้ (msg.sender.send (1 อีเธอร์)) สามารถทำให้ง่ายขึ้นเพื่อใช้การถ่ายโอน () เช่นเดียวกับใน msg.sender.transfer (1 อีเธอร์) เช็คเอาท์ บันทึกการเปลี่ยนแปลง Solidity สำหรับการเปลี่ยนแปลงที่คล้ายกันมากขึ้น.

โปรดทราบว่า “ในตัว” สามารถทำให้เป็นเงาได้

ปัจจุบันเป็นไปได้ที่จะ เงา globals ในตัวใน Solidity สิ่งนี้ช่วยให้สัญญาสามารถลบล้างฟังก์ชันการทำงานของบิวท์อินเช่น msg และ revert () แม้ว่าสิ่งนี้ มีวัตถุประสงค์, อาจทำให้ผู้ใช้เข้าใจผิดในสัญญาเกี่ยวกับพฤติกรรมที่แท้จริงของสัญญา.

สัญญา PretendingToRevert {function revert () internal constant {}} contract ExampleContract is PretendingToRevert {function somethingBad () public {revert (); }}

ผู้ใช้สัญญา (และผู้ตรวจสอบบัญชี) ควรทราบซอร์สโค้ดสัญญาอัจฉริยะแบบเต็มของแอปพลิเคชันใด ๆ ที่พวกเขาตั้งใจจะใช้.

หลีกเลี่ยงการใช้ tx.origin

อย่าใช้ tx.origin ในการอนุญาตสัญญาอื่นอาจมีวิธีการที่จะเรียกสัญญาของคุณ (เช่นผู้ใช้มีเงินทุนอยู่บ้าง) และสัญญาของคุณจะอนุญาตการทำธุรกรรมนั้นเนื่องจากที่อยู่ของคุณอยู่ใน tx.origin.

สัญญา MyContract {เจ้าของที่อยู่; ฟังก์ชัน MyContract () สาธารณะ {owner = msg.sender; } ฟังก์ชัน sendTo (ตัวรับที่อยู่จำนวน uint) สาธารณะ {ต้องการ (tx.origin == เจ้าของ); (bool success,) = receiver.call.value (จำนวน) (""); ต้องการ (ความสำเร็จ); }} สัญญา AttackingContract {MyContract myContract; ผู้โจมตีที่อยู่ ฟังก์ชัน AttackingContract (ที่อยู่ myContractAddress) สาธารณะ {myContract = MyContract (myContractAddress); ผู้โจมตี = msg.sender; } function () สาธารณะ {myContract.sendTo (ผู้โจมตี, msg.sender.balance); }} รหัสภาษา: JavaScript (javascript)

คุณควรใช้ msg.sender ในการให้สิทธิ์ (หากสัญญาอื่นเรียกสัญญา msg ผู้ส่งของคุณจะเป็นที่อยู่ของสัญญาไม่ใช่ที่อยู่ของผู้ใช้ที่เรียกสัญญา).

คุณสามารถอ่านเพิ่มเติมได้ที่นี่: เอกสาร Solidity

คำเตือน: นอกจากปัญหาเกี่ยวกับการอนุญาตแล้วยังมีโอกาสที่ tx.origin จะถูกลบออกจากโปรโตคอล Ethereum ในอนาคตดังนั้นโค้ดที่ใช้ tx.origin จะไม่สามารถใช้ร่วมกับรุ่นในอนาคตได้ Vitalik: “อย่าคิดว่า tx.origin จะยังคงใช้งานได้หรือมีความหมายต่อไป”

นอกจากนี้ยังควรค่าแก่การกล่าวถึงว่าการใช้ tx ที่มาคุณกำลัง จำกัด การทำงานร่วมกันระหว่างสัญญาเนื่องจากสัญญาที่ใช้ tx.origin ไม่สามารถใช้กับสัญญาอื่นได้เนื่องจากสัญญาไม่สามารถเป็น tx.origin ได้.

ดู SWC-115

การพึ่งพาการประทับเวลา

มีข้อควรพิจารณาหลักสามประการเมื่อใช้การประทับเวลาเพื่อเรียกใช้ฟังก์ชันที่สำคัญในสัญญาโดยเฉพาะอย่างยิ่งเมื่อการดำเนินการเกี่ยวข้องกับการโอนเงิน.

การจัดการการประทับเวลา

โปรดทราบว่าคนขุดแร่สามารถจัดการการประทับเวลาของบล็อกได้ พิจารณาสิ่งนี้ สัญญา:

uint256 เกลือส่วนตัวคงที่ = block.timestamp; ฟังก์ชั่นสุ่ม (uint Max) ผลตอบแทนส่วนตัวคงที่ (ผลลัพธ์ uint256) {// รับเมล็ดพันธุ์ที่ดีที่สุดสำหรับการสุ่ม uint256 x = salt * 100 / Max; uint256 y = เกลือ * block.number / (เกลือ% 5); uint256 seed = block.number / 3 + (salt% 300) + Last_Payout + y; uint256 h = uint256 (block.blockhash (เมล็ดพันธุ์)); กลับ uint256 ((h / x))% สูงสุด + 1; // สุ่มตัวเลขระหว่าง 1 ถึงสูงสุด} รหัสภาษา: PHP (php)

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

กฎ 15 วินาที

 กระดาษสีเหลือง (ข้อมูลจำเพาะอ้างอิงของ Ethereum) ไม่ได้ระบุข้อ จำกัด ว่าบล็อกสามารถลอยไปในเวลาเท่าใด แต่ มันระบุ การประทับเวลาแต่ละครั้งควรมีขนาดใหญ่กว่าการประทับเวลาของระดับบนสุด การใช้งานโปรโตคอล Ethereum ยอดนิยม Geth และ ความเท่าเทียมกัน ทั้งสองปฏิเสธบล็อกที่มีการประทับเวลานานกว่า 15 วินาทีในอนาคต ดังนั้นหลักเกณฑ์ที่ดีในการประเมินการใช้เวลาประทับคือ: หากขนาดของเหตุการณ์ที่ขึ้นอยู่กับเวลาของคุณอาจแตกต่างกันไป 15 วินาทีและคงไว้ซึ่งความสมบูรณ์คุณควรใช้ block.timestamp อย่างปลอดภัย.

หลีกเลี่ยงการใช้ block.number เป็น timestamp

เป็นไปได้ที่จะประมาณเดลต้าเวลาโดยใช้คุณสมบัติ block.number และ เวลาบล็อกเฉลี่ย, อย่างไรก็ตามนี่ไม่ใช่ข้อพิสูจน์ในอนาคตเนื่องจากเวลาบล็อกอาจเปลี่ยนแปลง (เช่น การจัดระเบียบส้อม และ ระเบิดความยาก). ในการขายที่มีระยะเวลาไม่กี่วันกฎ 15 วินาทีจะช่วยให้บรรลุการประมาณเวลาที่เชื่อถือได้มากขึ้น.

ดู SWC-116

ข้อควรระวังในการสืบทอดหลาย ๆ

เมื่อใช้การสืบทอดหลายรายการใน Solidity สิ่งสำคัญคือต้องเข้าใจว่าคอมไพเลอร์เขียนกราฟการสืบทอดอย่างไร.

สัญญาขั้นสุดท้าย {uint public a; ฟังก์ชั่นสุดท้าย (uint f) สาธารณะ {a = f; }} สัญญา B ถือเป็นค่าธรรมเนียมสาธารณะขั้นสุดท้าย {int; ฟังก์ชัน B (uint f) สุดท้าย (f) สาธารณะ {} ฟังก์ชัน setFee () สาธารณะ {ค่าธรรมเนียม = 3; }} สัญญา C คือค่าธรรมเนียมสาธารณะขั้นสุดท้าย {int; ฟังก์ชัน C (uint f) สุดท้าย (f) สาธารณะ {} ฟังก์ชัน setFee () สาธารณะ {ค่าธรรมเนียม = 5; }} สัญญา A คือ B, C {ฟังก์ชัน A () สาธารณะ B (3) C (5) {setFee (); }} ภาษารหัส: PHP (php)

เมื่อมีการใช้งานสัญญาคอมไพเลอร์จะทำให้การสืบทอดเป็นเส้นตรงจากขวาไปซ้าย (หลังจากที่คีย์เวิร์ดคือพาเรนต์จะแสดงรายการจากฐานที่เหมือนมากที่สุดไปจนถึงคำที่ได้รับมากที่สุด) นี่คือ Linearization ของสัญญา A:

สุดท้าย <- ข <- ค <- ก

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

เพื่อช่วยในการมีส่วนร่วม Solidity’s Github มี โครงการ กับปัญหาที่เกี่ยวข้องกับมรดกทั้งหมด.

ดู SWC-125

ใช้ประเภทอินเทอร์เฟซแทนที่อยู่เพื่อความปลอดภัยของประเภท

เมื่อฟังก์ชันใช้ที่อยู่สัญญาเป็นอาร์กิวเมนต์จะเป็นการดีกว่าที่จะส่งผ่านอินเทอร์เฟซหรือประเภทสัญญาแทนที่จะเป็นที่อยู่ดิบ หากฟังก์ชันถูกเรียกใช้ที่อื่นภายในซอร์สโค้ดคอมไพเลอร์จะให้การรับประกันความปลอดภัยเพิ่มเติม.

ที่นี่เราเห็นสองทางเลือก:

ตัวตรวจสอบสัญญา {function validate (uint) ผลตอบแทนภายนอก (bool); } สัญญา TypeSafeAuction {// ฟังก์ชันที่ดี validateBet (Validator _validator, uint _value) ผลตอบแทนภายใน (บูล) {bool valid = _validator.validate (_value); ส่งคืนที่ถูกต้อง; }} สัญญา TypeUnsafeAuction {// ฟังก์ชันไม่ดี validateBet (ที่อยู่ _addr, uint _value) ผลตอบแทนภายใน (บูล) {Validator validator = Validator (_addr); บูลถูกต้อง = validator.validate (_value); ส่งคืนที่ถูกต้อง; }} รหัสภาษา: JavaScript (javascript)

ประโยชน์ของการใช้สัญญา TypeSafeAuction ข้างต้นสามารถดูได้จากตัวอย่างต่อไปนี้ หาก validateBet () ถูกเรียกด้วยอาร์กิวเมนต์แอดเดรสหรือประเภทสัญญาอื่นที่ไม่ใช่ Validator คอมไพเลอร์จะแสดงข้อผิดพลาดนี้:

Contract NonValidator {} การประมูลสัญญาคือ TypeSafeAuction {NonValidator nonValidator; ฟังก์ชั่นเดิมพัน (uint _value) {bool valid = validateBet (nonValidator, _value); // TypeError: ประเภทไม่ถูกต้องสำหรับอาร์กิวเมนต์ในการเรียกใช้ฟังก์ชัน // การแปลงโดยนัยไม่ถูกต้องจากสัญญา NonValidator // ไปยัง Contract Validator ที่ร้องขอ }} รหัสภาษา: JavaScript (javascript)

หลีกเลี่ยงการใช้ extcodesize เพื่อตรวจสอบบัญชีที่เป็นเจ้าของภายนอก

ตัวแก้ไขต่อไปนี้ (หรือการตรวจสอบที่คล้ายกัน) มักใช้เพื่อตรวจสอบว่ามีการโทรจากบัญชีที่เป็นเจ้าของภายนอก (EOA) หรือบัญชีสัญญา:

// ตัวแก้ไขที่ไม่ดี isNotContract (ที่อยู่ _a) {uint size; แอสเซมบลี {size: = extcodesize (_a)} ต้องใช้ (size == 0); _; } ภาษารหัส: จาวาสคริปต์ (javascript)

แนวคิดตรงไปตรงมา: หากที่อยู่มีรหัสไม่ใช่ EOA แต่เป็นบัญชีสัญญา อย่างไรก็ตาม, สัญญาไม่มีซอร์สโค้ดในระหว่างการก่อสร้าง. ซึ่งหมายความว่าในขณะที่ตัวสร้างกำลังทำงานอยู่สามารถโทรไปยังสัญญาอื่น ๆ ได้ แต่การขยายขนาดสำหรับที่อยู่จะส่งกลับศูนย์ ด้านล่างนี้เป็นตัวอย่างเล็กน้อยที่แสดงให้เห็นว่าการตรวจสอบนี้สามารถหลีกเลี่ยงได้อย่างไร:

สัญญา OnlyForEOA {uint public flag; // ตัวแก้ไขที่ไม่ดี isNotContract (ที่อยู่ _a) {uint len; แอสเซมบลี {len: = extcodesize (_a)} ต้องการ (len == 0); _; } ฟังก์ชัน setFlag (uint i) isNotContract สาธารณะ (msg.sender) {flag = i; }} สัญญา FakeEOA {ตัวสร้าง (ที่อยู่ _a) สาธารณะ {OnlyForEOA c = OnlyForEOA (_a); ค. setFlag (1); }} รหัสภาษา: JavaScript (javascript)

เนื่องจากที่อยู่สัญญาสามารถคำนวณล่วงหน้าได้การตรวจสอบนี้อาจล้มเหลวหากตรวจสอบที่อยู่ซึ่งว่างเปล่าที่บล็อก n แต่มีสัญญาที่ใช้กับที่อยู่ในบางบล็อกที่มีค่ามากกว่า n.

คำเตือน: ปัญหานี้มีความเหมาะสม หากเป้าหมายของคุณคือการป้องกันไม่ให้สัญญาอื่น ๆ สามารถเรียกสัญญาของคุณได้การตรวจสอบขนาดภายนอกก็น่าจะเพียงพอแล้ว อีกทางเลือกหนึ่งคือการตรวจสอบค่าของ (tx.origin == msg.sender) แม้ว่าจะเป็นเช่นนี้ก็ตาม มีข้อเสีย.

อาจมีสถานการณ์อื่น ๆ ที่การตรวจสอบ Extcodesize ตอบสนองวัตถุประสงค์ของคุณ การอธิบายทั้งหมดนี้อยู่นอกขอบเขต ทำความเข้าใจพฤติกรรมพื้นฐานของ EVM และใช้วิจารณญาณของคุณ.

รหัส Blockchain ของคุณปลอดภัยหรือไม่?

จองการตรวจสอบเฉพาะจุด 1 วันกับผู้เชี่ยวชาญด้านความปลอดภัยของเรา จองวันนี้ความขยันความปลอดภัยสัญญาอัจฉริยะความปลอดภัยจดหมายข่าวสมัครรับจดหมายข่าวของเราเพื่อรับข่าวสารล่าสุดของ Ethereum โซลูชันระดับองค์กรทรัพยากรสำหรับนักพัฒนาและอื่น ๆ ที่อยู่อีเมลเนื้อหาพิเศษวิธีสร้างผลิตภัณฑ์ Blockchain ที่ประสบความสำเร็จการสัมมนาผ่านเว็บ

วิธีสร้างผลิตภัณฑ์ Blockchain ที่ประสบความสำเร็จ

วิธีการตั้งค่าและเรียกใช้โหนด Ethereumการสัมมนาผ่านเว็บ

วิธีการตั้งค่าและเรียกใช้โหนด Ethereum

วิธีสร้าง Ethereum API ของคุณเองการสัมมนาผ่านเว็บ

วิธีสร้าง Ethereum API ของคุณเอง

วิธีสร้างโซเชียลโทเค็นการสัมมนาผ่านเว็บ

วิธีสร้างโซเชียลโทเค็น

การใช้เครื่องมือรักษาความปลอดภัยในการพัฒนาสัญญาอัจฉริยะการสัมมนาผ่านเว็บ

การใช้เครื่องมือรักษาความปลอดภัยในการพัฒนาสัญญาอัจฉริยะ

อนาคตของการเงินสินทรัพย์ดิจิทัลและ DeFiการสัมมนาผ่านเว็บ

อนาคตของการเงิน: สินทรัพย์ดิจิทัลและ DeFi

Mike Owergreen Administrator
Sorry! The Author has not filled his profile.
follow me
Like this post? Please share to your friends:
Adblock
detector
map