การแยกโครงสร้างสัญญาอัจฉริยะ (2): การวิเคราะห์รหัสเมื่อสร้างและรันไทม์

บทความนี้เป็นส่วนที่สองของบทความชุด
หากคุณยังไม่ได้อ่านคำนำของบทความนี้ โปรดอ่านก่อนตอนที่ 1 บทนำ
เรากำลังแยกโครงสร้าง EVM bytecode ของสัญญาอัจฉริยะแบบ solidity อย่างง่าย
วันนี้เรามาเริ่มใช้กลยุทธ์ "แบ่งและพิชิต" เพื่อแยกรหัสที่ซับซ้อนของสัญญาอัจฉริยะ ดังที่ฉันได้กล่าวไว้ในคำนำเบื้องต้น โค้ดที่แยกส่วนนี้เป็นโค้ดระดับต่ำมาก แต่จะสามารถอ่านได้ค่อนข้างดีเมื่อเทียบกับโค้ดไบต์ดั้งเดิม
โปรดตรวจสอบให้แน่ใจว่าคุณได้ปฏิบัติตามการดำเนินการที่ฉันแนะนำในคำนำและนำรหัส BasicToken ไปใช้งานในคอมไพเลอร์รีมิกซ์
ข้อจำกัดความรับผิดชอบ: คำแนะนำทั้งหมดที่ระบุในบทความนี้ขึ้นอยู่กับการตีความของฉันเองเกี่ยวกับวิธีการทำธุรกรรมและไม่ได้แสดงถึงความคิดเห็นอย่างเป็นทางการของ Ethereum
ตอนนี้,อนุญาตเรามุ่งเน้นไปที่การดำเนินการ JUMP, JUMPI, JUMPDES, RETURN และ STOP และไม่สนใจการดำเนินการอื่นๆ ทั้งหมด เมื่อใดก็ตามที่เราพบ opcode ที่ไม่ใช่หนึ่งในนั้น เราก็แค่เพิกเฉยและข้ามไปยังคำสั่งถัดไปโดยไม่เข้าไปยุ่งกับมัน
เมื่อ EVM ดำเนินการโค้ด จะอยู่ในลำดับจากบนลงล่าง ไม่มีจุดเข้าอื่นในโค้ด และการดำเนินการจะเริ่มต้นจากด้านบนเสมอ JUMP และ JUMPI สามารถทำให้โค้ดกระโดดได้ JUMP รับค่าสูงสุดบนสแต็กและจะดำเนินการคำสั่งที่ย้ายไปยังตำแหน่งนั้น อย่างไรก็ตาม ตำแหน่งเป้าหมายต้องมี opcode JUMPDEST มิฉะนั้น การดำเนินการจะล้มเหลว จุดประสงค์เพียงอย่างเดียวคือ: JUMPDEST ทำเครื่องหมายตำแหน่งว่าเป็นเป้าหมายการข้ามที่ถูกต้อง JUMPI ก็เหมือนกันทุกประการ แต่ต้องไม่มี "0" ในตำแหน่งที่สองของสแต็ก มิฉะนั้นจะไม่มีการกระโดดดังนั้นนี่คือการกระโดดแบบมีเงื่อนไข STOP คือคำสั่งให้หยุดสัญญาอัจฉริยะอย่างสมบูรณ์ RETURN คือการระงับการดำเนินการของสัญญาอัจฉริยะ แต่ส่งคืนข้อมูลส่วนหนึ่งในหน่วยความจำ EVM ซึ่งสะดวกมาก。
ดังนั้นเรามาเริ่มอธิบายโค้ดโดยคำนึงถึงสิ่งเหล่านี้ทั้งหมด ในดีบักเกอร์ของ Remix ให้เลื่อนแถบเลื่อนสำหรับ "ธุรกรรม" ไปทางซ้ายจนสุด คุณสามารถใช้ปุ่ม Step Into (ดูเหมือนลูกศรชี้ลงเล็กๆ) แล้วทำตามคำแนะนำ
คุณสามารถละเว้นคำสั่งก่อนหน้าได้ ไปที่คำสั่งที่ 11 โดยตรง เราพบ JUMPI ตัวแรก หากไม่กระโดด มันจะดำเนินการต่อตามคำแนะนำที่ 12 ถึง 15 และเข้าสู่ REVERT ในที่สุด การดำเนินการจะหยุดลง แต่ถ้ากระโดด มันจะข้ามคำแนะนำเหล่านั้นไปยังตำแหน่ง 16 (ฐานสิบหก 0x0010 ซึ่งถูกผลักเข้าไปในสแต็กที่คำสั่ง 8) คำสั่ง 16 เป็น JUMPDEST
ดำเนินการต่อผ่าน opcodes จนกว่าแถบเลื่อน "ธุรกรรม" จะอยู่ทางขวาสุด blah มากมายเพิ่งเกิดขึ้น แต่ opcode ของ RETURN พบได้ในตำแหน่ง 68 เท่านั้น (และ opcode ในคำสั่ง STOP 69 ในกรณีเช่นนี้) นี่เป็นเรื่องแปลกมาก หากคุณลองคิดดู ขั้นตอนการควบคุมของสัญญาอัจฉริยะนี้จะสิ้นสุดที่คำสั่ง 15 หรือ 68 เสมอ เราเพิ่งเสร็จสิ้นและพบว่าไม่มีโฟลว์อื่นที่เป็นไปได้ ดังนั้นคำแนะนำที่เหลือคืออะไร (หากคุณปัดแผงคำสั่ง คุณจะเห็นรหัสสิ้นสุดที่ตำแหน่ง 566)
ชุดคำสั่ง (0 ถึง 69) ที่เราเพิ่งแนะนำคือสิ่งที่เรียกว่า "รหัสการสร้าง" ของสัญญา มันไม่เคยกลายเป็นส่วนหนึ่งของรหัสสัญญาอัจฉริยะ แต่จะถูกดำเนินการเพียงครั้งเดียวโดย EVM ในระหว่างการทำธุรกรรมที่สร้างสัญญาอัจฉริยะ ในขณะที่เราจะค้นพบในไม่ช้า รหัสนี้มีหน้าที่ในการตั้งค่าสถานะเริ่มต้นของสัญญาที่สร้างขึ้นและส่งคืนสำเนาของรหัสรันไทม์ คำสั่งที่เหลืออีก 497 รายการ (70 ถึง 566)ดังที่เราเห็น ขั้นตอนการดำเนินการไปไม่ถึง และรหัสนี้จะเป็นส่วนหนึ่งของสัญญาอัจฉริยะที่ปรับใช้。
ชื่อระดับแรก
สร้างส่วน
ตอนนี้เราจะเจาะลึกในส่วนการสร้างโค้ด

รูปที่ 1 การแยกโครงสร้าง EVM bytecode เวลาสร้างของ BasicToken.sol
นี่คือแนวคิดที่สำคัญที่สุดที่ต้องทำความเข้าใจในบทความนี้ รหัสการสร้างจะดำเนินการในธุรกรรม ซึ่งจะส่งคืนสำเนาของรหัสรันไทม์ ซึ่งเป็นรหัสจริงของสัญญาอัจฉริยะ ดังที่เราจะเห็น ตัวสร้างเป็นส่วนหนึ่งของรหัสการสร้าง ไม่ใช่รหัสรันไทม์ คอนสตรัคเตอร์ของสัญญาอัจฉริยะเป็นส่วนหนึ่งของโค้ดที่สร้างขึ้น เมื่อปรับใช้ จะไม่ปรากฏในโค้ดของสัญญาอัจฉริยะ
เวทมนตร์นี้เกิดขึ้นได้อย่างไร? นี่คือสิ่งที่เราจะวิเคราะห์ทีละขั้นตอนในตอนนี้
ตกลง ตอนนี้ปัญหาของเราลดลงเหลือแค่ทำความเข้าใจ 70 คำสั่งที่สอดคล้องกับรหัสเวลาสร้าง
กลับไปที่แนวทางจากบนลงล่าง คราวนี้รู้คำแนะนำทั้งหมดแทนที่จะข้ามขั้นตอนใดไป ก่อนอื่น เรามาเน้นที่คำแนะนำ 0 ถึง 2 ที่ใช้รหัส opcodes PUSH1 และ MSTORE

รูปที่ 2 โครงสร้างไบต์โค้ดตัวชี้หน่วยความจำ EVM ฟรี
PUSH1 เพียงแค่ผลักหนึ่งไบต์ไปที่ด้านบนของสแต็ก ในขณะที่ MSTORE คว้าสองรายการสุดท้ายจากสแต็กและจัดเก็บหนึ่งในนั้นไว้ในหน่วยความจำ:
mstore(0x40, 0x80)
| |
| What to store.
Where to store.
(in memory)
หมายเหตุ: ข้อมูลโค้ดด้านบนคือรหัส Yul-ish สังเกตว่ามันกินองค์ประกอบจากสแต็กจากซ้ายไปขวาอย่างไร โดยกินองค์ประกอบที่อยู่ด้านบนสุดของสแต็กก่อนเสมอ
นี่คือที่เก็บตัวเลข 0x80 (128 ทศนิยม) ที่ตำแหน่ง 0x40 (64 ทศนิยม)
เรื่องที่เรากำลังคุยกันอยู่นี้ ช่างมันเถอะ ถ้ามีเหตุผลจะอธิบายทีหลัง
ตอนนี้ เปิดพาเนลสแต็กและหน่วยความจำในแท็บดีบักเกอร์ของ Remix เพื่อให้คุณเห็นภาพคำแนะนำเหล่านี้เมื่อคุณทำตามขั้นตอน
คุณอาจสงสัยว่าเกิดอะไรขึ้นกับคำแนะนำที่ 1 และ 3 PUSH เป็นคำสั่ง EVM เดียวที่ประกอบด้วยสองไบต์ขึ้นไป ดังนั้น PUSH 80 จึงเป็นสองคำสั่ง ดังนั้นเราจึงไขปริศนา: คำสั่งที่ 1 คือ 0x80 และคำสั่งที่ 3 คือ 0x40
ต่อไปฉันจะอธิบายคำแนะนำตั้งแต่ 5 ถึง 15

รูปที่ 3 โครงสร้างไบต์โค้ด EVM ของเช็คที่ไม่ต้องชำระ
และนี่คือออปโค้ดใหม่จำนวนมาก: CALLVALUE, DUP1, ISZERO, PUSH2 และ REVERT CALLVALUE พุชจำนวน Wei ที่เกี่ยวข้องในการสร้างธุรกรรม DUP1 คัดลอกองค์ประกอบแรกในสแต็กและ ISZERO พุช 1 ไปที่สแต็กหากค่าสูงสุดของสแต็กเป็นศูนย์ PUSH2 เหมือนกับ PUSH1 แต่ดันสองไบต์ไปที่สแต็ก ในขณะที่ REVERT หยุดการดำเนินการ
แล้วเกิดอะไรขึ้นที่นี่? ใน Solidity เราสามารถเขียนแอสเซมบลีนี้ได้ดังนี้:
if(msg.value!= 0)revert();
รหัสนี้ไม่ได้เป็นส่วนหนึ่งของแหล่งที่มา Solidity ดั้งเดิมของเรา แต่ถูกแทรกโดยคอมไพเลอร์เนื่องจากเราไม่ได้ประกาศให้ผู้สร้างเป็นผู้ชำระ ใน Solidity เวอร์ชันล่าสุด ฟังก์ชันที่ไม่ได้ประกาศอย่างชัดเจนว่าต้องจ่ายจะไม่สามารถรับอีเธอร์ได้ กลับไปที่รหัสชุดประกอบ JUMPI ที่คำแนะนำ 11 จะข้ามคำแนะนำที่ 12 ถึง 15 หรือข้ามไปที่ 16 หากไม่มีอีเธอร์ที่เกี่ยวข้อง มิฉะนั้น REVERT จะดำเนินการโดยมีอาร์กิวเมนต์สองตัวเป็น 0 (หมายความว่าจะไม่มีการส่งคืนข้อมูลที่เป็นประโยชน์)
ตกลง! พักดื่มกาแฟกันเถอะ
(ส่วนต่อไปจะเป็นเรื่องยุ่งยากเล็กน้อย ดังนั้นควรพักสักครู่ก่อนจะกลับไปมีสมาธิอีกครั้ง หากาแฟดีๆ สักแก้วก่อน ให้แน่ใจว่าคุณเข้าใจสิ่งที่เราได้เห็นจนถึงตอนนี้ เพราะตอนต่อไปค่อนข้างซับซ้อน)
หากคุณต้องการวิธีอื่นในการแสดงภาพสิ่งที่เราเพิ่งเสร็จสิ้น ให้ลองใช้เครื่องมือง่ายๆ ที่ฉันสร้างขึ้นนี้: solmap ช่วยให้คุณสามารถคอมไพล์ Solidity code ได้ทันที จากนั้นคลิกที่ EVM opcode เพื่อไฮไลท์ Solidity code ที่เกี่ยวข้อง การแยกชิ้นส่วนนั้นแตกต่างจาก Remix เล็กน้อย แต่คุณควรจะเข้าใจได้โดยการเปรียบเทียบ

ได้เวลากาแฟแล้ว!
พร้อมที่จะก้าวต่อไป? ถัดไปคือคำแนะนำ 16 ถึง 37 โปรดใช้ดีบักเกอร์ของ Remix ต่อไป (จำไว้นะ รีมิกซ์คือเพื่อนที่ดีที่สุดของคุณ ^^).

รูปที่ 4 โครงสร้างรหัสไบต์ EVM สำหรับดึงพารามิเตอร์คอนสตรัคเตอร์จากรหัสที่ต่อท้ายรหัสไบต์ของสัญญาอัจฉริยะ
คำสั่งสี่คำสั่งแรก (17 ถึง 20) อ่านสิ่งที่อยู่ในหน่วยความจำที่ตำแหน่ง 0x40 แล้วกดลงบนสแต็ก หากคุณจำได้ นั่นควรเป็นตัวเลข 0x80 ต่อไปนี้พุช 0x20 (ทศนิยม 32) ลงบนสแต็ก (คำสั่ง 21) และคัดลอกค่านั้น (คำสั่ง 23) กด 0x0217 (ทศนิยม 535) (คำสั่ง 24) และสุดท้ายคัดลอกค่าที่สี่ (คำสั่ง 27) ซึ่งควรเป็น 0x80.
เมื่อดูคำสั่ง EVM เช่นนี้ ไม่เป็นไรที่จะมองข้ามสิ่งที่เกิดขึ้นไปชั่วขณะ ไม่ต้องกังวล มันจะผุดขึ้นมาในหัวของคุณเป็นครั้งคราว
ในคำสั่ง 28, CODECOPY จะถูกดำเนินการต้องใช้อาร์กิวเมนต์สามตัว: ตำแหน่งหน่วยความจำเป้าหมายที่เก็บโค้ดที่คัดลอก หมายเลขคำสั่งที่จะคัดลอก และจำนวนไบต์ของโค้ดที่จะคัดลอกดังนั้น ในกรณีนี้ 0x80 เริ่มต้นที่ตำแหน่งไบต์ที่อยู่ในโค้ด (535 ซึ่งเป็นตำแหน่งเป้าหมายของความยาวโค้ด 32 ไบต์)
หากคุณดูรหัสการถอดประกอบทั้งหมด จะมีคำแนะนำ 566 ข้อ เหตุใดรหัสนี้จึงพยายามคัดลอกรหัส 32 ไบต์ล่าสุด ในความเป็นจริง เมื่อปรับใช้สัญญาที่มีตัวสร้างพารามิเตอร์ พารามิเตอร์จะถูกต่อท้ายโค้ดเป็นข้อมูลดิบเลขฐานสิบหก (เลื่อนลงแผงคำอธิบายเพื่อดูข้อมูลนี้) ในกรณีนี้ ตัวสร้างใช้พารามิเตอร์ uint256 ดังนั้นโค้ดทั้งหมดนี้จึงคัดลอกพารามิเตอร์ไปยังหน่วยความจำจากค่าที่ต่อท้ายโค้ด
คำแนะนำ 32 ข้อเหล่านี้ไม่สมเหตุสมผลเหมือนรหัสที่แยกส่วน แต่แสดงเป็นฐานสิบหกดิบ: 0x000000000000000000000000...0000000000000000000002710 แน่นอนว่านี่คือค่าทศนิยม 10,000 ที่เราส่งต่อไปยังตัวสร้างเมื่อปรับใช้สัญญาอัจฉริยะ!
คุณสามารถทำซ้ำส่วนนี้ใน Remix ทีละขั้นตอน ให้แน่ใจว่าคุณเข้าใจว่าเพิ่งเกิดอะไรขึ้น ผลลัพธ์สุดท้ายควรเป็นตำแหน่ง 0x00..002710 ดูหมายเลข 0x80 ในหน่วยความจำ
ก่อนที่เราจะเริ่มส่วนต่อไป ฉันขอแนะนำให้พักดื่มวิสกี้สักแก้วก่อน

ชั่วโมงวิสกี้!
ทำไมถึงแนะนำให้คุณดื่มวิสกี้สักแก้ว เพราะจากที่นี่ ทุกอย่างตกต่ำ
ชุดคำสั่งถัดไปคือ 29 ถึง 35 ซึ่งอัปเดตค่า 0x80 ที่แอดเดรสหน่วยความจำ 0x40 เป็นค่า 0xa0 อย่างที่คุณเห็น ค่านี้จะชดเชยค่าเป็น 0x20 (32) ไบต์
ตอนนี้เราสามารถเริ่มเข้าใจคำสั่ง 0 ถึง 2 Solidity ติดตามสิ่งที่เรียกว่า "ตัวชี้ว่าง": สถานที่ในหน่วยความจำที่เราสามารถจัดเก็บบางสิ่ง รับประกันได้ว่าจะไม่มีใครเขียนทับมัน (เว้นแต่เราจะทำผิดพลาด) ดังนั้น เนื่องจากเราเก็บหมายเลข 10,000 ไว้ในตำแหน่งหน่วยความจำว่างเก่า เราจึงอัปเดตตัวชี้หน่วยความจำว่างโดยเลื่อนไปข้างหน้า 32 ไบต์
แม้แต่นักพัฒนา Solidity ที่ช่ำชองก็ยังสับสนเมื่อเห็น "ตัวชี้หน่วยความจำว่าง" หรือโค้ด mload(0x40, 0x80) ซึ่งเพียงแค่พูดว่า "เมื่อใดก็ตามที่เราเขียนรายการใหม่ เราจะเริ่มจากจุดนี้ เริ่มเขียนไปยังหน่วยความจำและเก็บค่าชดเชย บันทึก".
ทุกฟังก์ชันใน Solidity เมื่อคอมไพล์เป็น EVM bytecode จะเริ่มต้นตัวชี้นี้
มีอะไรอยู่ในหน่วยความจำระหว่าง 0x00 ถึง 0x40 คุณอาจไม่รู้ เลขที่ ส่วนของหน่วยความจำที่สงวนไว้โดย Solidity ซึ่งจะคำนวณค่าแฮช และเราจะเห็นในไม่ช้าว่าจำเป็นสำหรับแผนที่และข้อมูลไดนามิกประเภทอื่นๆ
ตอนนี้ ในคำสั่ง 37 MLOAD อ่านตำแหน่ง 0x40 จากหน่วยความจำ และโดยทั่วไปจะดาวน์โหลดค่าของเรา 10,000 จากหน่วยความจำไปยังสแต็ก ซึ่งจะเป็นค่าใหม่และพร้อมใช้งานในชุดคำสั่งถัดไป
นี่เป็นรูปแบบทั่วไปใน EVM bytecode ที่สร้างโดย Solidity: ก่อนที่เนื้อความของฟังก์ชันจะถูกเรียกใช้งาน พารามิเตอร์ของฟังก์ชันจะถูกโหลดลงในสแต็ก (หากเป็นไปได้) เพื่อให้โค้ดที่กำลังจะมาถึงสามารถใช้มันได้ ซึ่งเป็นสิ่งที่จะเกิดขึ้นต่อไป ที่เกิดขึ้น.
เรามาต่อด้วยคำอธิบาย 38 ถึง 55

รูปที่ 5 รหัส EVM หลักของตัวสร้าง
คำแนะนำเหล่านี้ไม่มีอะไรมากไปกว่าเนื้อหาของตัวสร้าง: นั่นคือรหัส Solidity:
totalSupply_ = _initialSupply;
balances[msg.sender] = _initialSupply;
คำสั่งสี่คำสั่งแรกค่อนข้างชัดเจน (38 ถึง 42) อันดับแรก 0 จะถูกผลักไปที่สแต็ก จากนั้นรายการที่สองบนสแต็กจะถูกคัดลอก (นี่คือหมายเลข 10,000 ของเรา) จากนั้นหมายเลข 0 จะถูกคัดลอกและกดลงบน สแต็กซึ่งเป็นช่องตำแหน่ง totalSupply_ ในที่เก็บข้อมูล ตอนนี้ SSTORE สามารถใช้ค่าเหล่านี้และยังคงเก็บไว้ต่ำกว่า 10,000 เพื่อใช้ในอนาคต:
sstore(0x00, 0x2710)
| |
| What to store.
Where to store.
(in storage)
ดู! เราเก็บตัวเลข 10,000 ไว้ในตัวแปร totalSupply_ น่าทึ่งมั้ยล่ะ??

อย่าลืมนึกภาพค่านี้ในแท็บ Debugger ของ Remix คุณสามารถค้นหาได้ในแผงโหลดเต็มของร้านค้า
คำแนะนำชุดถัดไป (43 ถึง 54) ค่อนข้างยุ่งยากกว่าเล็กน้อย แต่โดยพื้นฐานแล้วเกี่ยวข้องกับการจัดเก็บคีย์ msg.sender ที่ 10,000 ในแผนที่ยอดคงเหลือ ก่อนดำเนินการต่อ ตรวจสอบให้แน่ใจว่าคุณเข้าใจส่วนนี้ของเอกสารคู่มือ Solidity ซึ่งจะอธิบายวิธีเก็บแผนที่ไว้ในหน่วยความจำ
โดยสรุป จะเชื่อมต่อช่องของค่าที่แมป (ในกรณีนี้คือหมายเลข 1 เนื่องจากเป็นตัวแปรที่สองที่ประกาศในสัญญาอัจฉริยะ) กับคีย์ที่ใช้ (ในกรณีนี้คือ msg.sender ผ่าน opcode เพื่อรับ CALLER) จากนั้นทำการแยกย่อยด้วย SHA3 opcode และใช้เป็นปลายทางในหน่วยความจำ ในท้ายที่สุด พื้นที่จัดเก็บเป็นเพียงพจนานุกรมหรือตารางแฮชธรรมดาๆ
ทำตามคำแนะนำที่ 43 ถึง 45 ที่อยู่ msg.sender จะถูกเก็บไว้ในหน่วยความจำ (เวลานี้อยู่ที่ตำแหน่ง 0x00) จากนั้นในคำแนะนำที่ 46 ถึง 50 ค่า 1 (ช่องที่แมป) จะถูกเก็บไว้ที่ตำแหน่งหน่วยความจำ 0x20 สุดท้าย SHA3 opcode จะคำนวณ Keccak256 hash ของสิ่งใดๆ ในหน่วยความจำจากตำแหน่ง 0x00 ไปยังตำแหน่ง 0x40 นั่นคือการเชื่อมช่อง/ตำแหน่งที่แมปกับคีย์ที่ใช้ นี่คือจุดที่ค่า 10,000 จะถูกเก็บไว้ในแผนที่ของเรา:
sstore(hash..., 0x2710)
| |
| What to store.
Where to store.
ณ จุดนี้ ร่างกายของคอนสตรัคเตอร์ได้รับการดำเนินการอย่างสมบูรณ์แล้ว
ทั้งหมดนี้อาจดูล้นหลามเล็กน้อยในตอนแรก แต่เป็นส่วนสำคัญของการทำงานใน Solidity หากคุณไม่เข้าใจ ฉันขอแนะนำให้คุณทำตามด้วยดีบักเกอร์ของ Remix สองสามครั้ง โดยเก็บสแต็กและแผงหน่วยความจำไว้
นอกจากนี้ อย่าลังเลที่จะถามคำถามต่อไปนี้ รูปแบบนี้มักใช้ใน EVM bytecode ที่สร้างโดย Solidity และคุณจะเรียนรู้ได้อย่างรวดเร็วเพื่อจดจำรูปแบบนี้ได้อย่างง่ายดาย ในท้ายที่สุด ก็แค่คำนวณว่าตำแหน่งใดในหน่วยความจำที่จะเก็บค่าสำหรับคีย์เฉพาะของแผนที่

รูปที่ 6 โครงสร้างการจำลองรหัสรันไทม์
ในคำแนะนำ 56 ถึง 65 เราทำสำเนาโค้ดอีกครั้ง เฉพาะครั้งนี้ เราไม่คัดลอก 32 ไบต์สุดท้ายของรหัสลงในหน่วยความจำ เราคัดลอก 0x01d1 (465 ทศนิยม) ไบต์ที่เริ่มต้นที่ตำแหน่ง 0x0046 (70 ทศนิยม) ไปยังหน่วยความจำที่ตำแหน่ง 0 นั่นเป็นโค้ดก้อนใหญ่ที่ต้องทำซ้ำ!
หากคุณเลื่อนตัวเลื่อนไปทางขวาจนสุดอีกครั้ง คุณจะสังเกตเห็นว่าตำแหน่ง 70 อยู่ถัดจากโค้ด EVM เวลาบิวด์ของเรา ซึ่งการดำเนินการจะหยุดลง รหัสไบต์รันไทม์อยู่ภายใน 465 ไบต์เหล่านั้น นี่คือส่วนหนึ่งของรหัสที่จะบันทึกในบล็อกเชนเป็นรหัสรันไทม์ของสัญญาอัจฉริยะรหัสจะเป็นรหัสที่ดำเนินการทุกครั้งที่มีคนหรือบางสิ่งโต้ตอบกับสัญญาอัจฉริยะ(เราจะกล่าวถึงรันไทม์โค้ดในส่วนหลังของชุดนี้)
นั่นคือสิ่งที่คำสั่ง 66 ถึง 69 ทำ: ส่งคืนรหัสที่เราคัดลอกไปยังหน่วยความจำ

รูปที่ 7 รหัสรันไทม์ส่งคืนโครงสร้าง EVM bytecode
RETURN คว้ารหัสที่คัดลอกไปยังหน่วยความจำและส่งต่อไปยัง EVM หากรหัสการสร้างนี้ดำเนินการในบริบทของการทำธุรกรรมไปยังที่อยู่ 0x0 EVM จะดำเนินการรหัสและเก็บค่าส่งคืนเป็นรหัสรันไทม์ของสัญญาอัจฉริยะที่สร้างขึ้น
ถึงตอนนี้ โค้ด BasicToken ของเราจะสร้างและปรับใช้อินสแตนซ์สัญญาอัจฉริยะ ซึ่งพร้อมใช้งานกับสถานะเริ่มต้นและโค้ดรันไทม์ หากคุณย้อนกลับไปและดูรูปที่ 2 คุณจะเห็นว่าโครงสร้าง EVM bytecode ทั้งหมดที่เราวิเคราะห์เป็นแบบทั่วไป ยกเว้นโครงสร้างที่เน้นด้วยสีม่วง กล่าวคือ จะถูกสร้างขึ้นโดย Solidity compiler ในเวลาที่สร้าง bytecode . ตัวสร้างแตกต่างจากตัวสร้างเฉพาะในส่วนสีม่วง - เนื้อหาที่แท้จริงของตัวสร้าง โครงสร้างที่รับพารามิเตอร์ที่ฝังอยู่ที่ส่วนท้ายของ bytecode และที่คัดลอกโค้ดรันไทม์และส่งกลับ สามารถคิดได้ว่าเป็นโค้ดต้นแบบและโครงสร้าง opcode EVM ทั่วไป ตอนนี้คุณควรจะดูตัวสร้างใดๆ ได้ และคุณควรมีแนวคิดทั่วไปเกี่ยวกับส่วนประกอบที่ประกอบขึ้นก่อนที่จะทำตามคำแนะนำ
ในบทความถัดไปในชุดนี้ เราจะกล่าวถึงโค้ดรันไทม์จริง โดยเริ่มจากวิธีการโต้ตอบกับโค้ด EVM ของสัญญาอัจฉริยะที่จุดเข้าใช้งานต่างๆ ตอนนี้ให้ตบหลังให้ตัวเองเพราะคุณเพิ่งย่อยส่วนที่ยากที่สุดของซีรีส์ คุณควรมีความสามารถที่แข็งแกร่งในการอ่านและดีบัก EVM bytecode เข้าใจโครงสร้างทั่วไป และที่สำคัญที่สุดคือรู้ความแตกต่างระหว่าง build-time และ runtime EVM bytecode นี่คือสิ่งที่ทำให้ตัวสร้างสัญญามีความพิเศษใน Solidity
Cheetah blockchain security ใช้เทคโนโลยีของ Kingsoft Internet Security รวมกับปัญญาประดิษฐ์, nlp และเทคโนโลยีอื่นๆ เพื่อให้ผู้ใช้ blockchain ได้รับบริการด้านความปลอดภัยทางระบบนิเวศ เช่น การตรวจสอบสัญญาและการวิเคราะห์ความรู้สึก
*บทความนี้เผยแพร่ครั้งแรกบนสื่อโดย Alejandro Santander แปลและเรียบเรียงโดย Cheetah Blockchain*
Cheetah blockchain security ใช้เทคโนโลยีของ Kingsoft Internet Security รวมกับปัญญาประดิษฐ์, nlp และเทคโนโลยีอื่นๆ เพื่อให้ผู้ใช้ blockchain ได้รับบริการด้านความปลอดภัยทางระบบนิเวศ เช่น การตรวจสอบสัญญาและการวิเคราะห์ความรู้สึก
เว็บไซต์อย่างเป็นทางการของ Ratingtoken https://www.ratingtoken.net/?from=z



