สัญญาพร็อกซีเป็นเครื่องมือสำคัญสำหรับนักพัฒนาสัญญาอัจฉริยะ ปัจจุบัน มีโหมดพร็อกซีและกฎการใช้งานที่สอดคล้องกันมากมายในระบบสัญญา ก่อนหน้านี้เราได้สรุปแนวทางปฏิบัติที่ดีที่สุดด้านความปลอดภัยของสัญญาพร็อกซีที่อัปเกรดได้
ในบทความนี้ เราจะแนะนำโมเดลพร็อกซีอีกรูปแบบหนึ่งที่ได้รับความนิยมในชุมชนนักพัฒนา นั่นคือโมเดลไดมอนด์พร็อกซี
สัญญาเพชรพร็อกซี่หรือที่เรียกว่า "เพชร" เป็นรูปแบบการออกแบบสำหรับ Ethereum smart contract ที่นำเสนอโดย Ethereum Improvement Proposal (EIP) 2535
โหมดไดมอนด์ช่วยให้สัญญามีฟังก์ชันไม่จำกัดโดยแบ่งฟังก์ชันออกเป็นสัญญาเล็กๆ (เรียกอีกอย่างว่า "ลักษณะ") ไดมอนด์ทำหน้าที่เป็นพร็อกซี ฟังก์ชันการกำหนดเส้นทางจะเรียกไปยังลักษณะที่เหมาะสม
การออกแบบโมเดลเพชรสามารถแก้ปัญหาการจำกัดขนาดสัญญาสูงสุดของเครือข่าย Ethereum การแบ่งสัญญาขนาดใหญ่ออกเป็นส่วนย่อยๆ รูปแบบข้าวหลามตัดช่วยให้นักพัฒนาสามารถสร้างสัญญาอัจฉริยะที่ซับซ้อนและเต็มไปด้วยฟีเจอร์ได้มากขึ้นโดยไม่ได้รับผลกระทบจากข้อจำกัดด้านขนาด
Diamond Brokerage มอบความยืดหยุ่นอย่างมากเมื่อเทียบกับสัญญาที่อัปเกรดได้แบบดั้งเดิม ช่วยให้ชิ้นส่วนสัญญาสามารถอัพเกรด เพิ่ม แทนที่ หรือลบส่วนของฟังก์ชันที่เลือกโดยไม่ต้องแตะต้องส่วนอื่น
บทความนี้ให้ภาพรวมของ EIP-2535 รวมถึงการเปรียบเทียบกับโหมดพร็อกซีโปร่งใสที่ใช้กันอย่างแพร่หลายและโหมดพร็อกซี UUPS และข้อควรพิจารณาด้านความปลอดภัยสำหรับชุมชนนักพัฒนา
ในบริบทของ EIP-2535"เพชร"เป็นสัญญาพร็อกซีที่มีการใช้งานฟังก์ชันโดยสัญญาลอจิกที่แตกต่างกัน ซึ่งเรียกว่า "ลักษณะ"
ลองนึกภาพว่าเพชรแท้มีด้านต่างๆ ที่เรียกว่า facets และสัญญาเพชร Ethereum ที่สอดคล้องกันก็มีด้านต่างๆ กัน ฟังก์ชั่นการยืมเพชรแต่ละสัญญานั้นมีด้านหรือแง่มุมที่แตกต่างกัน
มาตรฐานเพชรใช้การเปรียบเทียบเพื่อขยายขีดความสามารถของ "การเจียระไนเพชร" เพื่อเพิ่ม แทนที่ หรือลบเหลี่ยมเพชรพลอยและคุณลักษณะต่างๆ
นอกจากนี้ Diamond Standard ยังมีคุณสมบัติที่เรียกว่า "Diamond Loupe" ซึ่งจะส่งคืนข้อมูลเกี่ยวกับเหลี่ยมเพชรพลอยและการมีอยู่ของเพชร
เมื่อเทียบกับรูปแบบพร็อกซีแบบดั้งเดิม "ไดมอนด์" เทียบเท่ากับสัญญาตัวแทน และ "ลักษณะ" ที่แตกต่างกันสอดคล้องกับการทำให้สัญญาเป็นจริง ลักษณะต่างๆ ของไดมอนด์เอเจนต์สามารถแชร์ฟังก์ชันภายใน ไลบรารี และตัวแปรสถานะได้ ส่วนประกอบที่สำคัญของเพชรมีดังนี้
สัญญากลางที่ทำหน้าที่เป็นพร็อกซี ฟังก์ชันการกำหนดเส้นทางเรียกไปยังลักษณะที่เหมาะสม ประกอบด้วยการแมปตัวเลือกฟังก์ชันกับที่อยู่ "ลักษณะ"
สัญญาเดียวที่ใช้ฟังก์ชันเฉพาะ แต่ละด้านประกอบด้วยชุดของฟังก์ชันที่เพชรเรียกได้
เป็นชุดของฟังก์ชันมาตรฐานที่กำหนดใน EIP-2535 ซึ่งให้ข้อมูลเกี่ยวกับตัวเลือกด้านและฟังก์ชันที่ใช้ในเพชร Diamond Loupe ช่วยให้นักพัฒนาและผู้ใช้ตรวจสอบและทำความเข้าใจโครงสร้างของเพชรได้
ฟังก์ชันสำหรับเพิ่ม แทนที่ หรือลบเหลี่ยมเพชรและตัวเลือกคุณลักษณะที่เกี่ยวข้อง เฉพาะที่อยู่ที่ได้รับอนุญาต (เช่น เจ้าของเพชรหรือสัญญาหลายลายเซ็น) เท่านั้นที่จะทำการเจียระไนเพชรได้
เช่นเดียวกับพร็อกซีแบบดั้งเดิม เมื่อมีการเรียกใช้ฟังก์ชันบนพร็อกซีไดมอนด์ ฟังก์ชันทางเลือกของพร็อกซี (ฟังก์ชันสำรอง) จะถูกเรียกใช้งาน ข้อแตกต่างหลักจากพร็อกซีไดมอนด์คือในฟังก์ชันสำรอง มีการแมป SelectorToFacet ซึ่งจัดเก็บและกำหนดว่าที่อยู่สัญญาแบบลอจิคัลใดมีการใช้งานฟังก์ชันที่เรียกว่า จากนั้นจะใช้การเรียกผู้รับมอบสิทธิ์เพื่อดำเนินการฟังก์ชัน เช่นเดียวกับพร็อกซีแบบดั้งเดิม
พร็อกซีทั้งหมดใช้ฟังก์ชัน fallback() ซึ่งมอบสิทธิ์การเรียกใช้ฟังก์ชันไปยังที่อยู่ภายนอก ด้านล่างนี้คือการใช้งานพร็อกซีไดมอนด์และการใช้งานพร็อกซีแบบดั้งเดิม
เป็นที่น่าสังเกตว่าบล็อกรหัสชุดประกอบนั้นคล้ายกันมาก ดังนั้นข้อแตกต่างเพียงอย่างเดียวคือแอดเดรสด้านในการเรียกผู้ร่วมประชุมไดมอนด์พร็อกซีและแอดเดรสโดยนัยในการเรียกผู้ร่วมประชุมพร็อกซีแบบดั้งเดิม
ข้อแตกต่างที่สำคัญคือในพร็อกซีรูปเพชร ที่อยู่ของลักษณะจะถูกกำหนดโดยแฮชแมปจาก msg.sig ของผู้โทร (ตัวเลือกฟังก์ชัน) ไปยังที่อยู่ของลักษณะ ขณะที่ในพร็อกซีแบบดั้งเดิม ที่อยู่โดยนัยจะไม่ขึ้นอยู่กับ ของผู้โทรเข้ามา
ฟังก์ชั่นสำรองตัวแทนเพชร
ฟังก์ชันสำรองพร็อกซีแบบดั้งเดิม
การแมป SelectorToFacet กำหนดว่าสัญญาใดมีการใช้งานตัวเลือกฟังก์ชันแต่ละตัว ผู้ปฏิบัติงานโครงการมักจำเป็นต้องเพิ่ม แทนที่ หรือลบการแมปสัญญาตัวเลือกการนำไปใช้งานของฟังก์ชันนี้ EIP-2535 ระบุว่า: เพื่อให้บรรลุเป้าหมายนี้ ต้องมีฟังก์ชัน diamondCut() ด้านล่างนี้คืออินเทอร์เฟซตัวอย่าง
โครงสร้าง FacetCut แต่ละโครงสร้างมีที่อยู่ของ facet และอาร์เรย์ตัวเลือกคุณลักษณะสี่ไบต์ที่จะอัปเดตในสัญญาพร็อกซีรูปเพชร FaceCutAction ช่วยให้สามารถเพิ่ม แทนที่ และลบตัวเลือกคุณสมบัติได้ การใช้ฟังก์ชัน diamondCut() ควรรวมถึงการควบคุมการเข้าถึงที่เพียงพอ ป้องกันการชนกันของช่อง การกู้คืนเมื่อล้มเหลว เป็นต้น
เพื่อสอบถามว่าตัวแทนเพชรมีหน้าที่และลักษณะอย่างไร เราจึงใช้ "แว่นขยายเพชร" "Diamond Loupe" มีลักษณะพิเศษที่ใช้อินเทอร์เฟซต่อไปนี้ที่กำหนดไว้ใน EIP-2535:
ฟังก์ชัน facets() ควรส่งคืนที่อยู่ของ facets ทั้งหมดและตัวเลือกฟังก์ชันสี่ไบต์ ฟังก์ชัน facetFunctionSelectors() ควรส่งคืนตัวเลือกฟังก์ชันทั้งหมดที่รองรับโดยลักษณะเฉพาะ ฟังก์ชัน facetAddresses() ควรส่งคืนที่อยู่ facet ทั้งหมดที่ใช้โดยเพชร
ฟังก์ชัน facetAddress() ควรส่งคืนส่วนที่สนับสนุนตัวเลือกที่กำหนด หรือที่อยู่ (0) หากไม่พบ โปรดทราบว่าไม่ควรมีที่อยู่ด้านกว้างมากกว่าหนึ่งรายการที่มีตัวเลือกคุณลักษณะเดียวกัน
เนื่องจากพร็อกซี่ไดมอนด์มอบหมายการเรียกใช้ฟังก์ชันที่แตกต่างกันให้กับสัญญาการใช้งานที่แตกต่างกัน จึงจำเป็นอย่างยิ่งที่จะต้องจัดการช่องจัดเก็บข้อมูลอย่างเหมาะสมเพื่อป้องกันความขัดแย้ง EIP-2535 กล่าวถึงวิธีการจัดการช่องเก็บข้อมูลหลายวิธี
ด้านนี้สามารถประกาศตัวแปรสถานะในโครงสร้าง ลักษณะนี้สามารถใช้โครงสร้างจำนวนเท่าใดก็ได้ โดยแต่ละส่วนมีตำแหน่งจัดเก็บต่างกัน แต่ละโครงสร้างมีตำแหน่งเฉพาะในการจัดเก็บสัญญา Aspects สามารถประกาศตัวแปรสถานะของตัวเองได้ แต่ไม่สามารถขัดแย้งกับตำแหน่งที่เก็บข้อมูลของตัวแปรสถานะที่ประกาศโดยด้านอื่นๆ ห้องสมุดตัวอย่างและสัญญาการจัดเก็บเพชรมีอยู่ใน EIP-2535 ดังแสดงในรูปต่อไปนี้:
App Store เป็น Diamond Store เวอร์ชันพิเศษกว่า รูปแบบนี้ใช้เพื่อแบ่งปันตัวแปรสถานะของลักษณะต่างๆ ได้สะดวกและง่ายดายยิ่งขึ้น โครงสร้าง App Store ถูกกำหนดให้มีจำนวนและประเภทของตัวแปรสถานะที่แอปพลิเคชันต้องการ ลักษณะจะประกาศโครงสร้าง AppStorage เป็นตัวแปรสถานะตัวแรกและตัวเดียวเสมอ ที่ตำแหน่ง 0 ของสล็อตที่เก็บข้อมูล ด้านต่างๆจึงสามารถเข้าถึงตัวแปรจากโครงสร้างนี้ได้
ชื่อเรื่องรอง
เปรียบเทียบกับ Transparent Proxy และ UUPS Proxy
โหมดพร็อกซีหลักสองโหมดที่ใช้โดยชุมชนนักพัฒนา Web3 คือโหมดพร็อกซีโปร่งใสและโหมดพร็อกซี UUPS ในส่วนนี้ เราจะเปรียบเทียบโหมดไดมอนด์พร็อกซีกับโหมดพร็อกซีแบบโปร่งใสและโหมดพร็อกซี UUPS โดยสังเขป
1.EPI-2535:https://eips.ethereum.org/EIPS/eip-2535 #Facets,% 20 State% 20 Variables% 20 and% 20 Diamond% 20 Storage
2.EPI-1967:https://eips.ethereum.org/EIPS/eip-1967
3.Diamond proxy reference implementation: https://github.com/mudgen/Diamond
4.OpenZeppelin implementation: https://github.com/OpenZeppelin/openzeppelin-contracts/tree/v4.7.0/contracts/proxy
พร็อกซีและโซลูชันที่ปรับขนาดได้เป็นระบบที่ซับซ้อนมากขึ้น และ OpenZeppelin จัดเตรียมฐานรหัสและเอกสารที่ครอบคลุมสำหรับพร็อกซีที่ปรับขนาดได้ของ UUPS, Transparent และ Beacon อย่างไรก็ตาม สำหรับรูปแบบ Diamond Proxy ในขณะที่ OpenZeppelin ยืนยันถึงประโยชน์ของมัน พวกเขายังคงตัดสินใจที่จะไม่รวมการใช้งาน EIP-2535 Diamond ในไลบรารีของพวกเขา
ดังนั้น นักพัฒนาที่ใช้ไลบรารีของบุคคลที่สามที่มีอยู่หรือนำโซลูชันนี้ไปใช้เองจะต้องดำเนินการด้วยความระมัดระวังอย่างยิ่ง ที่นี่เราได้รวบรวมรายการตรวจสอบแนวทางปฏิบัติที่ดีที่สุดด้านความปลอดภัยสำหรับชุมชนนักพัฒนาซอฟต์แวร์เพื่อพิจารณา
การแบ่งตรรกะของสัญญาออกเป็นโมดูลที่เล็กลงและสามารถจัดการได้มากขึ้น นักพัฒนาสามารถทดสอบและตรวจสอบรหัสของตนได้ง่ายขึ้น
คำอธิบายภาพ
ที่มา: Aavegotchi Github
คำอธิบายภาพ
ที่มา: Mugen's Diamond-3-Hardhat
เมื่อเพิ่มตัวแปรสถานะใหม่ให้กับโครงสร้างการจัดเก็บข้อมูลในสัญญาอัจฉริยะ จะต้องเพิ่มตัวแปรดังกล่าวที่ส่วนท้ายของโครงสร้าง การเพิ่มตัวแปรสถานะใหม่ที่จุดเริ่มต้นหรือตรงกลางของโครงสร้างจะทำให้ตัวแปรสถานะใหม่เขียนทับข้อมูลตัวแปรสถานะที่มีอยู่ และตัวแปรสถานะใดๆ หลังจากตัวแปรสถานะใหม่อาจอ้างถึงตำแหน่งหน่วยความจำที่ไม่ถูกต้อง
รูปแบบ AppStorage กำหนดให้มีการประกาศโครงสร้างเดียวและโครงสร้างเดียวสำหรับไดมอนด์พร็อกซี และโครงสร้างนี้ต้องแชร์โดยทุกด้าน หากจำเป็นต้องมีโครงสร้างหลายส่วน ควรใช้รูปแบบ DiamondStorage
อย่าใส่ struct โดยตรงใน struct อื่น เว้นแต่คุณจะแน่ใจว่าคุณไม่ต้องการเพิ่มตัวแปร state ให้กับ struct ภายใน ไม่สามารถเพิ่มตัวแปรสถานะใหม่ให้กับโครงสร้างภายในในการอัพเกรดโดยไม่ต้องเขียนทับช่องเก็บตัวแปรที่ประกาศหลังโครงสร้าง
วิธีแก้ปัญหาคือเพิ่มตัวแปรสถานะใหม่ให้กับโครงสร้างที่แมปหน่วยความจำแทนการวาง "struct" โดยตรงใน "struct" ช่องจัดเก็บตัวแปรในแผนที่มีการคำนวณต่างกันและไม่ได้อยู่ติดกันในที่จัดเก็บ
ขนาดของอาร์เรย์จะได้รับผลกระทบจากขนาดของโครงสร้าง เมื่อมีการเพิ่มตัวแปรสถานะใหม่ลงในโครงสร้าง ตัวแปรดังกล่าวจะเปลี่ยนขนาดและเค้าโครงของโครงสร้างนั้น
สิ่งนี้อาจทำให้เกิดปัญหาหากใช้โครงสร้างเป็นองค์ประกอบในอาร์เรย์ ถ้าขนาดและเค้าโครงของโครงสร้างเปลี่ยนไป ขนาดและเค้าโครงของอาร์เรย์ก็จะเปลี่ยนไปด้วย ซึ่งอาจทำให้เกิดปัญหากับการจัดทำดัชนีหรือการดำเนินการอื่นๆ ที่ต้องใช้ขนาดและเค้าโครงที่สอดคล้องกันของโครงสร้าง
เช่นเดียวกับรูปแบบพร็อกซีอื่นๆ ตัวแปรแต่ละตัวควรมีช่องจัดเก็บที่ไม่ซ้ำกัน มิฉะนั้น โครงสร้างที่แตกต่างกันสองแห่งในตำแหน่งเดียวกันจะเขียนทับซึ่งกันและกัน
ฟังก์ชัน initialize() มักจะใช้เพื่อตั้งค่าตัวแปรที่สำคัญ เช่น ที่อยู่ของบทบาทที่มีสิทธิพิเศษ หากไม่ได้เริ่มต้นเมื่อใช้งานสัญญา ผู้ประสงค์ร้ายสามารถโทรและควบคุมสัญญาได้
ขอแนะนำให้เพิ่มการควบคุมการเข้าถึงที่เหมาะสมให้กับฟังก์ชันการเริ่มต้น/การตั้งค่า หรือตรวจสอบให้แน่ใจว่ามีการเรียกใช้ฟังก์ชันเมื่อสัญญาถูกปรับใช้และไม่สามารถเรียกได้อีก
หากส่วนใดของสัญญาสามารถเรียกใช้ฟังก์ชัน selfdestruct() ได้ ก็อาจทำลายทั้งสัญญาได้ ส่งผลให้สูญเสียเงินทุนหรือข้อมูล สิ่งนี้เป็นอันตรายอย่างยิ่งในโหมดพร็อกซีไดมอนด์ เนื่องจากมีหลายแง่มุมที่สามารถเข้าถึงพื้นที่เก็บข้อมูลและข้อมูลของสัญญาพร็อกซีได้
ปัจจุบัน เราเห็นโครงการจำนวนมากขึ้นเรื่อยๆ ที่ใช้โมเดลพร็อกซีเพชรในสัญญาอัจฉริยะของพวกเขา มอบความยืดหยุ่นและข้อดีอื่นๆ ที่เหนือกว่าผู้รับมอบฉันทะแบบดั้งเดิม
อย่างไรก็ตาม ความยืดหยุ่นที่เพิ่มขึ้นอาจหมายถึงพื้นผิวการโจมตีที่กว้างขึ้นสำหรับผู้โจมตี เราหวังว่าบทความนี้จะช่วยให้ชุมชนนักพัฒนาเข้าใจกลไกของโมเดลพร็อกซีไดมอนด์และข้อควรพิจารณาด้านความปลอดภัย
ในขณะเดียวกัน ทีมงานโครงการควรทำการทดสอบอย่างเข้มงวดและการตรวจสอบจากบุคคลที่สามเพื่อลดความเสี่ยงของช่องโหว่ที่เกี่ยวข้องกับการดำเนินการตามสัญญาตัวแทนเพชร
CertiK จะเผยแพร่บทความทางเทคนิคดังกล่าวต่อไปเพื่อช่วยให้นักพัฒนาจำนวนมากขึ้นสามารถพัฒนาได้อย่างปลอดภัย ติดตามเราเพื่อรับข้อมูลและข้อมูลที่คล้ายกันมากขึ้น!


