บทความนี้จะแนะนำเฉพาะประเด็นที่เกี่ยวข้องกับการควบคุมอำนาจในสัญญาอัจฉริยะของ Rust จากสองมุมมองต่อไปนี้:
การมองเห็นวิธีการทำสัญญา (ฟังก์ชัน) การเข้าถึง/การโทร
ชื่อเรื่องรอง
1. ฟังก์ชั่นสัญญา (วิธีการ) การมองเห็น
เมื่อเขียนสัญญาอัจฉริยะ เราสามารถควบคุมฟังก์ชันที่สามารถเรียกโดยใครได้โดยการระบุการมองเห็นของฟังก์ชันสัญญา สิ่งนี้ช่วยให้เราสามารถปกป้องส่วนสำคัญของสัญญาได้อย่างง่ายดายจากการเข้าถึงหรือการจัดการโดยไม่ตั้งใจ
เพื่อสะท้อนถึงความสำคัญของการตั้งค่าการเปิดเผยฟังก์ชั่นสัญญาอย่างถูกต้อง บทความนี้จะยกตัวอย่างการแลกเปลี่ยนเครือข่าย Bancor เร็วที่สุดเท่าที่ 18 มิถุนายน 2020 เหตุการณ์ด้านความปลอดภัยของสินทรัพย์สัญญาเกิดขึ้นในการแลกเปลี่ยนเนื่องจากการตั้งค่าสิทธิ์การควบคุมการเข้าถึงที่ไม่ถูกต้องสำหรับฟังก์ชันหลักของสัญญา สัญญาเขียนด้วยภาษา Solidity ในภาษานี้ การเปิดเผยการทำงานของสัญญาจะแบ่งคร่าวๆ เป็นสาธารณะ/ภายนอก และส่วนตัว/ภายใน อดีตอนุญาตให้เรียกฟังก์ชันสัญญาโดยผู้โทรภายนอก ซึ่งถือได้ว่าเป็นส่วนหนึ่งของอินเทอร์เฟซสัญญา
อย่างไรก็ตาม เมื่อการแลกเปลี่ยนเครือข่าย Bancor แก้ไขช่องโหว่ด้านความปลอดภัยบางอย่าง เนื่องจากความประมาทเลินเล่อ จึงตั้งค่าฟังก์ชันการถ่ายโอนคีย์บางอย่างในสัญญาเป็นแอตทริบิวต์สาธารณะโดยไม่ได้ตั้งใจ (ตามที่แสดงด้านล่าง):
ตามนี้ ทุกคนรวมถึงผู้ใช้ทั่วไปสามารถเรียกใช้ฟังก์ชันเหล่านี้จากภายนอกสัญญาเพื่อดำเนินการถ่ายโอนที่เกี่ยวข้องสำหรับตนเองหรือผู้อื่น
การมีอยู่ของช่องโหว่ที่สำคัญนี้ทำให้สินทรัพย์ของผู้ใช้มูลค่า 590,000 ดอลลาร์ตกอยู่ในความเสี่ยงร้ายแรง
ในทำนองเดียวกันในสัญญาสมาร์ทของ Rust จะต้องให้ความสนใจกับการควบคุมการมองเห็นของฟังก์ชั่นสัญญา
ในชุดของสัญญาสมาร์ทในการพัฒนาไดอารี่ไดอารี่การพัฒนาสัญญาสมาร์ทสนิม (1)เราได้แนะนำมาโครที่กำหนดโดย NEAR SDK: #[near_bindgen]:
#[near_bindgen] ถูกกำหนดโดยฟังก์ชัน near_bindgen ในแพ็คเกจ near-sdk-macros-version ซึ่งเป็นตำแหน่งที่ใช้มาโครเพื่อสร้างโค้ดการฉีดโดยอัตโนมัติ (โค้ดฉีดมาโครที่สร้างอัตโนมัติ หรือเรียกสั้นๆ ว่า MAGIC)
จากการดูเอกสารรายละเอียดอย่างเป็นทางการที่จัดทำโดย NEAR เราจะเห็นว่ามีแอตทริบิวต์ที่มองเห็นได้หลายอย่างในฟังก์ชันสัญญาอัจฉริยะของ Rust ที่กำหนดโดยมาโคร #[near_bindgen]:
pub fn: ระบุว่าวิธีการของสัญญาเป็นแบบสาธารณะและเป็นส่วนหนึ่งของอินเทอร์เฟซของสัญญา ซึ่งหมายความว่าทุกคนสามารถเรียกใช้จากภายนอกสัญญาได้
fn: หากฟังก์ชัน method ของสัญญาไม่ได้ระบุ pub ไว้อย่างชัดเจน หมายความว่าฟังก์ชันนั้นไม่สามารถเรียกได้โดยตรงจากภายนอกสัญญา และฟังก์ชันอื่นในสัญญาจะเรียกภายในได้เท่านั้น
pub(crate) fn: เทียบเท่ากับ pub(in crate) คล้ายกับ fn ตัวปรับแต่งการมองเห็นนี้สามารถจำกัดวิธีสัญญาเฉพาะที่จะเรียกใช้ภายในขอบเขตของลัง
อีกวิธีหนึ่งในการกำหนดวิธีการของสัญญาเป็นการภายในคือการกำหนดบล็อครหัสสัญญาโดยนัยแยกต่างหากในสัญญา
แต่ควรสังเกตว่าการใช้งานไม่ได้ถูกแก้ไขโดย #[near_bindgen]:
การควบคุมการเข้าถึงสำหรับฟังก์ชันการโทรกลับ (Callbacks):
คำจำกัดความของฟังก์ชันการโทรกลับในสัญญาต้องตั้งค่าเป็นแอตทริบิวต์สาธารณะ เพื่อให้สามารถเรียกใช้ผ่านการเรียกใช้ฟังก์ชันได้
เมื่อเรากำหนดฟังก์ชันการโทรกลับในสัญญา เรายังจำเป็นต้องตรวจสอบให้แน่ใจว่าผู้อื่นไม่สามารถเรียกฟังก์ชันการโทรกลับได้ตามต้องการ นั่นคือ ผู้เรียกใช้ฟังก์ชันการโทรกลับ env::current_account_id() ต้องเป็น env::current_account_id() ของสัญญาเอง
NEAR SDK กำหนดมาโครสนิม #[ส่วนตัว] ที่เทียบเท่าสำหรับเรา การใช้มาโครนี้ ฟังก์ชันการเรียกกลับของสัญญาสามารถใช้ฟังก์ชันเดียวกันกับที่ใช้ในบรรทัดที่ 4-5 ของโค้ดด้านบน
ตามค่าเริ่มต้น ทุกอย่างในภาษา Rust จะเป็นแบบส่วนตัว ตัวอย่างเช่น ฟังก์ชัน fn ที่ไม่ได้ตั้งค่าแอตทริบิวต์สาธารณะด้านบนจะมีการมองเห็นเริ่มต้นเป็นส่วนตัว
สิ่งที่ solidity จำเป็นต้องแยกแยะในที่นี้คือใน solidty compiler รุ่นเก่าบางรุ่น: หากไม่มีการเพิ่มตัวแก้ไขในคำจำกัดความของฟังก์ชัน contract ตัวแก้ไขนั้นจะถือเป็นแบบสาธารณะตามค่าเริ่มต้น
แต่ในภาษา Rust มีข้อยกเว้นสองประการ:
รายการย่อยใน Pub Trait เป็นแบบสาธารณะตามค่าเริ่มต้น
ตัวแปร Enum ในผับ Enum ยังเป็นสาธารณะตามค่าเริ่มต้น
2. การควบคุมการเข้าถึงฟังก์ชันพิเศษ (กลไกรายการสีขาว)
เมื่อเขียนสัญญา Rust smart นอกเหนือจากการรู้การมองเห็นฟังก์ชันเฉพาะแล้ว เรายังจำเป็นต้องคิดอย่างลึกซึ้งจากระดับความหมายของสัญญา นั่นคือ เพื่อสร้างกลไกรายการควบคุมการเข้าถึงทั้งหมด
คล้ายกับสัญญา/การเข้าถึง/Ownable.sol ที่กำหนดและใช้ในไลบรารีสัญญาอัจฉริยะ Solidity openzeppelin-contract บางฟังก์ชันเป็นฟังก์ชันพิเศษ เช่น การเริ่มต้นสัญญา การเปิด/หยุดสัญญา การถ่ายโอนแบบรวม ฯลฯ... ... สามารถเรียกใช้โดยเจ้าของสัญญาเท่านั้น ฟังก์ชันเหล่านี้มักจะเรียกว่าเฉพาะฟังก์ชันเจ้าของเท่านั้น
แต่โดยพื้นฐานแล้วเจ้าของเป็นผู้เรียกภายนอกของสัญญา ในการเรียก ฟังก์ชันหลักเหล่านี้ต้องตั้งค่าเป็นคุณสมบัติสาธารณะ ดังนั้น เนื่องจากฟังก์ชันเหล่านี้เป็นแอตทริบิวต์สาธารณะ จึงหมายความว่าผู้ใช้ทั่วไปคนอื่นๆ สามารถมาเรียกใช้งานได้ด้วยหรือไม่
คำตอบคือใช่ แต่ผู้ใช้ทั่วไปที่ไม่ใช่เจ้าของจะพบว่าสามารถปรับได้ แต่ไม่สมบูรณ์
เนื่องจากในสัญญาอัจฉริยะ กฎการควบคุมการเข้าถึงบางข้อสามารถกำหนดได้สำหรับฟังก์ชันของสัญญา และต้องปฏิบัติตามกฎที่เกี่ยวข้องจึงจะได้รับอนุญาตให้ดำเนินการได้อย่างสมบูรณ์ ตัวอย่างเช่น มีตัวดัดแปลงที่ใช้กันทั่วไปต่อไปนี้ในสัญญาที่มั่นคง:
เมื่อมีการเรียกใช้ฟังก์ชันสัญญาที่แก้ไขโดยตัวแก้ไขนี้ ก่อนอื่นจะตรวจสอบว่าผู้โทร msg.sender ของธุรกรรมนี้เป็นเจ้าของที่ตั้งค่าไว้เมื่อเริ่มต้นสัญญาหรือไม่ หากไม่ตรงกัน การดำเนินการฟังก์ชันที่ตามมาจะยกเลิกหรือเปลี่ยนกลับ จึงช่วยป้องกันผู้ใช้ที่ไม่ได้รับอนุญาตจากการเข้าถึงและดำเนินการ
ในทำนองเดียวกัน ในสัญญาอันชาญฉลาดของ NEAR Rust เรายังสามารถใช้คุณลักษณะแบบกำหนดเองที่คล้ายกับต่อไปนี้:
การใช้ลักษณะนี้ยังสามารถใช้การควบคุมการเข้าถึงไปยังฟังก์ชันพิเศษบางอย่างในสัญญา กล่าวคือ ผู้เรียก env::predecessor_account_id() ของสัญญาในธุรกรรมนี้จะต้องเท่ากับเจ้าของสัญญา
ข้างต้น เราได้สร้างตัวอย่างรายการที่อนุญาตพิเศษอย่างง่ายสำหรับฟังก์ชันที่มีสิทธิพิเศษที่เป็นเจ้าของได้เท่านั้น ตามหลักการนี้ เราสามารถตั้งค่าผู้ใช้หลายคนในรายการที่อนุญาตพิเศษโดยปรับแต่งตัวดัดแปลงหรือลักษณะที่ซับซ้อนมากขึ้น หรือตั้งค่ารายการที่อนุญาตพิเศษหลายรายการเพื่อให้ได้เอฟเฟกต์การควบคุมการเข้าถึงกลุ่มที่ดีและละเอียด
3. วิธีการควบคุมการเข้าถึงเพิ่มเติม
วิธีการควบคุมการเข้าถึงในสัญญาอัจฉริยะของ Rust เช่น:
การควบคุมเวลาการโทรตามสัญญา
กลไกการเรียกหลายลายเซ็นของฟังก์ชันสัญญา การดำเนินการตามธรรมาภิบาล (DAO)
...
โปรดให้ความสนใจกับการติดตามผลของไดอารี่การพัฒนาสัญญาอัจฉริยะชุดนี้😊
