lang
简体中文
繁體中文
English
Tiếng Việt
한국어
日本語
ภาษาไทย
Türkçe
หน้าแรก
AI
OPRR
ด่วน
ความลึก
กิจกรรม
BlockBeats Pro
เพิ่มเติม
การเงิน
พิเศษ
ระบบนิเวศบล็อกเชน
รายการ
พอดแคสต์
ข้อมูล
BTC
$96,000
5.73%
ETH
$3,521.91
3.97%
HTX
$0.{5}2273
5.23%
SOL
$198.17
3.05%
BNB
$710
3.05%

กับดักบ่อพิษ Uniswap เก็บ 130,000 ดอลลาร์ใน 29 ชั่วโมง

อ่านบทความนี้ใน 39 นาที
ฮุก Uniswap v4 หนึ่งตัวเสนอราคาให้กับ aggregator ด้วยค่าธรรมเนียม 0% แต่เรียกเก็บ 12.8% กับธุรกรรมจริง เก็บเงียบๆ ไปประมาณ 130,000 ดอลลาร์ใน 29 ชั่วโมง
ชื่อเรื่องต้นฉบับ: 《เสนอราคา 0% แต่นำไปใช้จริง 12.8%: บทวิเคราะห์ย้อนหลังกับดักอัตราค่าธรรมเนียมแบบไดนามิกของ Uniswap v4 "บ่อพิษ"》
ผู้เขียนต้นฉบับ: หวังจื่อหาว, BitsLab


ตั้งแต่วันที่ 22 ถึง 23 สิงหาคม 2026 บ่ออัตราค่าธรรมเนียมแบบไดนามิก USDC/WBNB บน Uniswap v4 บน BNB Chain แสดงอัตราค่าธรรมเนียม 0% ในการเสนอราคาของตัวรวม แต่เมื่อผู้ใช้ทำธุรกรรมจริงกลับถูกเรียกเก็บ LP fee 12.8% ภายในเวลาประมาณ 29 ชั่วโมง บ่อนี้มีธุรกรรม 21,086 รายการ ค่าธรรมเนียมสะสม 131,888 ดอลลาร์ จากนั้นก็ถูกยกเลิก ธุรกรรมที่เสียหายทั้งหมดสำเร็จ: สัญญาไม่ถูกโจรกรรม ผลลัพธ์สูงกว่าขั้นต่ำที่ผู้ใช้กำหนด ไม่มีสัญญาณผิดปกติใด ๆ — ส่วนต่างกลายเป็นค่าธรรมเนียมอย่างเงียบ ๆ


บทความนี้ใช้ธุรกรรมตัวอย่างหนึ่งรายการเป็นจุดเริ่มต้น:

(0x7615dec2ce80506ec461a47739eb8533ac2bc7c605ca56c255964481b76363d0: ผู้ใช้ใส่ 5,926.90 USDT ได้รับน้อยกว่าที่คาดไว้ 757.43 USDT) เพื่อแยกวิเคราะห์ว่าบ่อนี้ทำได้อย่างไร "เสนอราคา 0% แต่นำไปใช้จริง 12.8%": การตัดสินสามชั้นของฟังก์ชันอัตราค่าธรรมเนียม การป้องกัน slippage ที่ถูกทำให้เป็นโมฆะ และ whitelist ที่กีดกัน LP รายอื่นออกไป ข้อสรุปทั้งหมดอ้างอิงจากข้อมูลบนเชนและการตรวจสอบแบบควบคุมในเครื่อง


01 เส้นทางจากการเสนอราคาสู่การนำไปใช้


หลังจากผู้ใช้เริ่มแลกเปลี่ยนบนหน้าจอตัวรวม เส้นทางทั้งหมดจะเป็นเช่นนี้:



บนเส้นทางนี้มีข้อเท็จจริงพื้นฐานสองประการ ซึ่งการตัดสินในแต่ละชั้นถัดไปล้วนตั้งอยู่บนสองจุดนี้


ก่อนอื่นดูฝั่ง v4: มันอนุญาตให้ Hook เขียนทับอัตราค่าธรรมเนียมทีละธุรกรรม PoolKey.fee = 0x800000 ประกาศบ่ออัตราค่าธรรมเนียมแบบไดนามิก; Hook ส่งค่า 0x400000 | fee กลับมาในค่าส่งคืนตัวที่สามของ beforeSwap โค้ดใน v4-core ที่รับค่าส่งคืนนี้ (Pool.sol:303-305):



isOverride() ตรวจสอบแฟล็กการเขียนทับ, removeOverrideFlagAndValidate() ถอด 0x400000 ออกและตรวจสอบว่าไม่เกิน MAX_LP_FEE = 1,000,000 (100%) จากนั้นดำเนินการ swap นี้ตามอัตราค่าธรรมเนียมนั้นและเขียนลงในเหตุการณ์ Swap กล่าวคือ Hook ส่งอะไรกลับมา ผู้ใช้ก็จ่ายตามนั้น — ตราบใดที่ผลลัพธ์ยังสูงกว่า amountOutMin นี่คือฟีเจอร์อย่างเป็นทางการ การปรับอัตราค่าธรรมเนียมตามความผันผวนและสินค้าคงคลังล้วนเป็นการใช้งานที่ชอบด้วยเหตุผล


มาดูที่ Hook นี้เอง: มันประกาศเพียงสองสิทธิ์เท่านั้น v4 เข้ารหัสสิทธิ์ไว้ใน 14 บิตล่างของที่อยู่合约 (Hooks.sol:29-47) ตรวจสอบตอน deploy และแก้ไขไม่ได้ภายหลัง คำจำกัดความ flag ฝั่ง v4-core สอดคล้องกับการถอดรหัสที่อยู่:



ทดสอบบนเชน getHookPermissions() ส่งค่ากลับตรงกับ bitmap: มีเพียงสองบิตนี้ที่เป็นจริง


Hook ไม่มีซอร์สโค้ด บทความนี้ decompile จาก bytecode ออกมาเป็นฟังก์ชันค่าธรรมเนียมทั้งฉบับ——ต่อไปนี้คือการแยกแยะพฤติกรรมของ logic ที่รันบนเชนแล้วนี้:



ต่อไปดูทีละชั้น สองชั้นแรกตอบคำถามว่า "นี่คือการจำลองหรือไม่" ชั้นที่สามกำหนดว่าธุรกรรมจริงจ่ายเท่าไหร่


02 การตัดสินสามชั้นของฟังก์ชันค่าธรรมเนียม


การตัดสินที่หนึ่ง: แยกแยะผู้เรียก


หลังจากฟังก์ชันค่าธรรมเนียมถูกเรียก จะเปรียบเทียบ tx.origin ก่อน: หากเท่ากับที่อยู่คงที่ใด ๆ ในสามที่อยู่ จะคืนค่า fallback tier ทันที fallback tier ปัจจุบันตั้งค่าเป็น 0%


สามที่อยู่ไม่ได้เลือกมาแบบสุ่ม เมื่อ eth_call ไม่ระบุ from origin ของธุรกรรมจะตกอยู่ที่ zero address——aggregator ที่สอบถามราคาแบบ batch มักไม่ระบุ ตรวจสอบโดยตรงใน local fork:



ชั้นแรกยอมรับเพียงสามที่อยู่นี้ ชั้นถัดไปดูว่าหลังจากระบุที่อยู่ปกติแล้ว Hook ยังแยกแยะได้อย่างไร


การตัดสินที่สอง: ตรวจสอบ gas


เปลี่ยน tx.origin เป็นที่อยู่ปกติแล้วลองใหม่ ผลลัพธ์ยังเป็น 0%——ชั้นแรกถูกข้ามไปแล้ว แต่ค่าธรรมเนียมไม่เปลี่ยน แสดงว่าชั้นแรกไม่ใช่การตัดสินเดียว ระบุไปที่ชั้นที่สอง: gasleft() * 10100 / 10000 >= gate


aggregator 报价มักให้ gas สูงมาก เพื่อป้องกันเส้นทาง候选ล้มเหลวในการจำลองเพราะ gas ไม่พอ ก่อนที่ธุรกรรมจริงจะเข้าสู่ Hook gas ถูกใช้ไปแล้วโดย Router, การอนุมัติ, การกระโดด前置 โค้ดเดียวกัน สามระดับ gas:



เส้นแบ่ง实测อยู่ระหว่าง 16.85M–16.9M gas ความแตกต่างระหว่าง 30M กับ 2.59M คือความแตกต่างระหว่าง "报价" กับ "การ execute"


เกณฑ์ที่สาม: การสุ่มเทียมแบบรายธุรกรรม


ธุรกรรมจริงที่ผ่านสองชั้นแรกจะเข้าสู่ชั้นที่สาม: นำลายนิ้วมือสภาพแวดล้อมบล็อกมาแฮชแล้วมอดูโลกับ 10,000 จากนั้นคืนค่าอัตราตามช่วงที่ผลลัพธ์ตกอยู่ การตั้งค่าปัจจุบันมีสามระดับ 8% / 10% / 10% หากไม่เข้าเงื่อนไขใดจะถอยกลับเป็น 0%


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


ในเหตุการณ์บนเชนทั้งหมด 21,086 รายการพบอัตราย้อนหลัง 19 ระดับ (6.8%–28%) แสดงว่าระดับอัตราถูกกำหนดค่าใหม่โดยฟังก์ชันผู้ดูแลตามความจำเป็นมาตลอด อัตราที่มีผลในขณะที่ธุรกรรมตัวอย่างจับคู่สำเร็จคือ 12.8%


ภาพรวมของการรวมสามชั้น



ถึงตรงนี้สามารถให้นิยามของ "พูลพิษ" ได้แล้ว — พูล v4 ที่ตรงตามเงื่อนไขสามข้อพร้อมกัน: PoolKey.fee มีธงอัตราแบบไดนามิก; การตัดสินใจเรื่องอัตราอ่านสัญญาณจากสภาพแวดล้อมการดำเนินการ (gas, origin, สภาพแวดล้อมบล็อก) แทนที่จะอ่านสถานะตลาดสาธารณะ; อัตราจากการจำลองใบเสนอราคากับการดำเนินการบนเชนเกิดการแยกออกจากกันอย่างเป็นระบบ โดยผู้ได้ประโยชน์คือผู้ déployer พูลพิษ เส้นแบ่งอยู่ที่ว่าอัตราอ่านอะไร: สิ่งที่คืนอัตราเดียวกันให้ผู้เรียกทุกรายคือการออกแบบตลาดที่ตั้งโปรแกรมได้; สิ่งที่คืนอัตราต่างกันตาม "ใครเป็นคนถามราคา" คือการหลอกลวงระบบเราเตอร์


ถึงตรงนี้ กลไกของการแยกอัตราก็ครบถ้วนแล้ว แต่หลังจากเก็บอัตราไปแล้ว ทำไมธุรกรรมยังสำเร็จได้ นั่นคือคำถามถัดไป


03 ทำไมการป้องกัน slippage ถึงไม่ถอยกลับ


ก่อนอื่นดูเส้นทางและจำนวนเงินที่สมบูรณ์ของธุรกรรมนี้:



เหตุการณ์ Swap ของการกระโดดนี้บันทึกอัตราที่มีผลไว้ในฟิลด์ fee: 128000 หน่วยอัตราของ v4 ใช้ 1,000,000 เป็น 100% ดังนั้น 128000 คือ 12.8% ความสูญเสียของธุรกรรมนี้สามารถวัดได้จากสองมุม: จากมุมของพูล อัตราถูกเรียกเก็บจากอินพุต WBNB; จากมุมของเส้นทางทั้งหมด ผลต่างเข้า-ออกของ stablecoin คือ 757.43 USDT คิดเป็นการสูญเสีย 12.7794% ซึ่งยังรวมค่าธรรมเนียมการกระโดดล่วงหน้าและความเบี่ยงเบนจากการหลุด peg ตัวเลขสองตัวต่างกันเพียง 0.02 เปอร์เซ็นต์ แสดงว่าแหล่งที่มาหลักของการสูญเสียคือ LP fee ของพูลนี้


amountOutMin ตรวจสอบเพียงขอบเขตล่างของผลลัพธ์สุดท้าย ไม่ดูว่าแต่ละการกระโดดตรงกลางเก็บค่าธรรมเนียมไปเท่าไร:



อัตราค่าธรรมเนียม 12.8% ตกอยู่ภายในบัฟเฟอร์ 20.32% ทั้งหมด ผลลัพธ์ยังคงสูงกว่าเส้น底线——ธุรกรรมสำเร็จ ไม่ย้อนกลับ เส้นทาง stablecoin กระแสหลักโดยทั่วไปมีค่าเริ่มต้นที่ 0.1%–1% เส้นทางนี้ให้พื้นที่มากกว่า 20 เท่า ผู้ใช้คิดว่าบัฟเฟอร์กว้างคือ "มั่นคงกว่า" แต่จริงๆ แล้วคือการมอบสัญญาทุกตัวบนเส้นทางให้ฝ่ายตรงข้ามกำกับตนเอง วาดบัญชีนี้เป็นภาพ:



04 การดำเนินการของผู้部署บ่อพิษ


ไวท์ลิสต์: กีดกัน LP รายอื่นออกไป


ตรรกะไวท์ลิสต์อยู่ใน beforeAddLiquidity ผลการ decompile มีดังนี้:



การเพิ่มสภาพคล่องต้องผ่านสามด่าน: เริ่มจากตรวจสอบการเรียก PoolManager ที่ Hook v4 ทุกตัวมี จากนั้นต้องผ่าน PositionManager อย่างเป็นทางการ สุดท้ายตรวจสอบไวท์ลิสต์ของผู้ถือโพซิชัน ที่อยู่ที่ไม่อยู่ในรายชื่อจะถูกสัญญาย้อนกลับตรงนี้ LP รายอื่นเข้ามาไม่ได้ รายได้ค่าธรรมเนียมจะไม่ถูกเจือจาง เป็นของผู้部署ทั้งหมด รายชื่อเองถูกดูแลโดยฟังก์ชันผู้ดูแล (selector 0xc4452e52) ตามที่อยู่


ระดับ 0% กับการปั่นปริมาณ


เมื่อนำธุรกรรม 21,086 รายการมามองรวมกัน การกระจายแสดงรูปแบบที่ชัดเจน: ระดับ 0% จำนวน 6,946 รายการ มูลค่าซื้อขาย 12,947,751 ดอลลาร์ เฉลี่ย 1,864 ดอลลาร์ต่อรายการ; ระดับเก็บค่าธรรมเนียม 14,140 รายการ มูลค่าซื้อขาย 1,120,106 ดอลลาร์ เฉลี่ย 79 ดอลลาร์ต่อรายการ ธุรกรรมขนาดใหญ่อยู่ในระดับ 0% ค่าธรรมเนียมกระจุกอยู่ที่ธุรกรรมขนาดเล็ก คำอธิบายที่สมเหตุสมผลที่สุดสำหรับขนาดใหญ่ 0% คือผู้部署ซื้อขายกับตัวเองด้วย gas สูง: gas สูงและการจับคู่ราคาของ aggregator ใช้การตัดสินเดียวกัน ต้นทุนค่าธรรมเนียมของการซื้อขายกับตัวเองใกล้ศูนย์ ปริมาณที่ปั่นขึ้นมาดันบ่อขึ้นอันดับบนเว็บข้อมูลตลาด (สแนปช็อตแสดงปริมาณซื้อขาย 24 ชม. ประมาณ 10.82 ล้านดอลลาร์ 16,671 รายการ) ในสายตา aggregator นี่คือบ่อที่มีสภาพคล่องดีและค่าธรรมเนียมต่ำ


ระดับค่าธรรมเนียมปรับใหม่ตามต้องการ


การตัดสินที่สามกล่าวถึงระดับค่าธรรมเนียมในอดีต 19 ระดับ มาจากฟังก์ชันตั้งค่าค่าธรรมเนียมที่ decompile ได้ (selector 0x4d909a45 ไม่มีในลายเซ็นสาธารณะ):



ระดับค่าธรรมเนียมสี่ระดับและค่า threshold ถูกแพ็ครวมกัน เขียนลงใน storage slot keccak(poolId, 2) จากการวัดบนเชน slot นี้มีค่า 0x0138800186a00186a0 ซึ่งสอดคล้องกับรูปแบบการแพ็คของฟังก์ชันทีละส่วน ระดับค่าธรรมเนียมที่นี่เป็นพารามิเตอร์ที่ปรับได้ตลอดเวลา ไม่ใช่ค่าที่กำหนดตายตัวตอน部署


วงจรชีวิต


พูลนี้สร้างเมื่อ 08-22 06:57,swap ครั้งแรกเกิดขึ้นเมื่อ 07:13,21:39 คือธุรกรรมตัวอย่างของบทความนี้;08-23 12:25 คือการซื้อขายครั้งสุดท้าย หลังจากนั้นก็ถูกยกเลิก ระยะเวลาที่ aktif ทั้งหมดประมาณ 29 ชั่วโมง ปริมาณการซื้อขายรวม 14,067,857 ดอลลาร์ รายได้ค่าธรรมเนียม 131,888 ดอลลาร์ (ยังไม่หักต้นทุนของผู้ deploy) เมื่อตรวจสอบซ้ำ สถานะการลงทะเบียนกลับมาเป็นเปิดใช้งานแล้ว——พูลยังอยู่ พร้อมที่จะเปิดใช้งานอีกครั้งได้ทุกเมื่อ


05 เส้นทางที่สมบูรณ์


เชื่อมโยงห่วงโซ่การโจมตีทั้งหมดเข้าด้วยกัน:



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


06 การแก้ไขและข้อเสนอแนะ


จุดแรกอยู่ที่วิธีการเสนอราคาของ aggregator สาเหตุรากคือการจำลองและการดำเนินการไม่ได้เดินเส้นทางเดียวกัน: การเสนอราคาใช้การ query ระดับพูลในอุดมคติ ควบคู่กับ zero address และ gas สูง;ธุรกรรมจริงมีตัวตนจริงและ gas ที่ถูกใช้ไปแล้ว Hook สามารถอ่านความแตกต่างเหล่านี้ได้พอดี อัตราค่าธรรมเนียมจึงแยกออกจากกัน วิธีแก้คือทำให้การจำลองใกล้เคียงกับการดำเนินการ——ใช้ Router calldata ที่จะส่งจริง ใช้ from/to จริง และใช้ gas ที่ใกล้เคียงกับธุรกรรมบนเชนในการจำลอง;หลังจากซื้อขายเสร็จ ให้ถอดรหัสเหตุการณ์ Swap เพื่อตรวจสอบ fee หากไม่ตรงกับราคาที่เสนอ ให้ลดน้ำหนักหรือถอดออก


จุดที่สองอยู่ที่การตั้งค่า slippage ของผู้ใช้ สาเหตุรากคือ min_out ตั้งไว้หลวมเกินไป: บัฟเฟอร์ 20.32% กลืนอัตราค่าธรรมเนียม 12.8% ทั้งหมด ธุรกรรมจึงยังสำเร็จตามปกติ เส้นทาง stablecoin กระแสหลัก 0.1%–1% เพียงพอสำหรับการใช้งานประจำวัน ก่อนส่งธุรกรรมให้ดูตัวเลขนี้สักครั้ง กระเป๋าเงินและ frontend ควรกระชับค่าเริ่มต้นให้แน่นขึ้น ซึ่งจะช่วยป้องกันความเสี่ยงส่วนใหญ่ให้ผู้ใช้ได้


จุดที่สามอยู่ที่การรับเข้าสู่เส้นทาง (routing admission) สาเหตุรากคือพูลที่มีอัตราค่าธรรมเนียมแบบไดนามิกใดก็ตามสามารถเข้าร่วมการแข่งขันเสนอราคาได้โดยตรง: Hook ที่ไม่มี open source ไม่มีบันทึกการ audit ก็สามารถเข้าสู่เส้นทางที่แนะนำได้ด้วยปริมาณการซื้อขายที่ปั่นขึ้นมา สำหรับพูลที่มีอัตราค่าธรรมเนียมแบบไดนามิกที่ไม่มีประวัติที่น่าเชื่อถือ การไม่ให้เข้าเส้นทางโดยค่าเริ่มต้นหรือลดน้ำหนักอย่างมีนัยสำคัญคือวิธีที่ปลอดภัยที่สุด


ข้อมูลและคำอธิบาย


·ธุรกรรมตัวอย่าง:

https://bscscan.com/tx/0x7615dec2ce80506ec461a47739eb8533ac2bc7c605ca56c255964481b76363d0 (บล็อก 117497524)


·ธุรกรรมการเริ่มต้นพูล:

https://bscscan.com/tx/0x8fa72ef72d77b61f715dc9ce90548249ab045c6d78716078cd5ab425bbe69a05


·PoolManager:

0x28e2ea090877bf75740558f6bfb36a5ffee9e9df


·Pool ID:

0x36e5540e9dedc02229fe8a82aa5b10c0bf07d1fa74e4f2ffe0efd00fa1a36aea


·Hook:

0xd111b3ddd92e627f1864520c770e913ec04e0880 (ไม่มีซอร์สโค้ด โค้ดเทียมเป็นการดีคอมไพล์อิสระในบทความนี้; ผู้ดูแล 0x08b03e1a5444d469f4dc954e74d3f662c94a6b13)


·การซื้อขายและค่าธรรมเนียม: เหตุการณ์ Swap ทั้งหมด 21,086 รายการในพูลนี้ถูกสะสมทีละรายการ (eth_getLogs) โดย WBNB คำนวณตามราคาที่ใช้ดำเนินการในรายการเดียวกัน; รายได้เป็นมูลค่าขั้นต้น ยังไม่ได้หักต้นทุนของผู้ปรับใช้


ลิงก์ต้นฉบับ


ยินดีต้อนรับสู่ชุมชนทางการของ BlockBeats:

กลุ่ม Telegram สมัครสมาชิก: https://t.me/theblockbeats

กลุ่ม Telegram พูดคุย: https://t.me/BlockBeats_App

บัญชี Twitter ทางการ: https://twitter.com/BlockBeatsAsia

举报 แก้ไข/รายงาน
เลือกคลัง
เพิ่มคลัง
ยกเลิก
เสร็จสิ้น
เพิ่มคลัง
เห็นได้เฉพาะตัวเอง
สาธารณะ
บันทึก
แก้ไข/รายงาน
ส่ง