Balancer ถูกแฮ็ก การวิเคราะห์ช่องโหว่
- 核心观点:Balancer因精度损失与不变值操控漏洞遭攻击。
- 关键要素:
- 小额交易缩放函数向下舍入致精度损失。
- 不变值D计算依赖缩放后余额,误差放大。
- 缺少不变值变化验证与批处理误差累积。
- 市场影响:引发DeFi协议安全机制与审计标准反思。
- 时效性标注:中期影响
คำนำ
เมื่อวันที่ 3 พฤศจิกายน 2568 โปรโตคอล Balancer ถูกโจมตีโดยแฮกเกอร์บนเครือข่ายสาธารณะหลายแห่ง เช่น Arbitrum และ Ethereum ส่งผลให้สูญเสียสินทรัพย์มูลค่า 120 ล้านดอลลาร์สหรัฐฯ แก่นของการโจมตีเกิดจากช่องโหว่สองประการ ได้แก่ การสูญเสียความแม่นยำ (precision loss) และการจัดการที่ไม่แปรเปลี่ยน (invariant manipulation)
ประเด็นสำคัญของการโจมตีครั้งนี้อยู่ที่ตรรกะของโปรโตคอลในการจัดการธุรกรรมขนาดเล็ก เมื่อผู้ใช้ทำการแลกเปลี่ยนมูลค่าเล็กน้อย โปรโตคอลจะเรียกใช้ฟังก์ชัน _upscaleArray ซึ่งใช้ mulDown เพื่อปัดเศษค่าลง หากยอดคงเหลือในธุรกรรมและจำนวนเงินที่ป้อนเข้ามาอยู่ในขอบเขตการปัดเศษที่กำหนด (เช่น ช่วง 8-9 wei) จะเกิดข้อผิดพลาดด้านความแม่นยำสัมพัทธ์อย่างมีนัยสำคัญ
ข้อผิดพลาดด้านความแม่นยำที่แพร่กระจายไปยังการคำนวณค่าคงที่ D ในโปรโตคอลทำให้ค่า D ลดลงอย่างผิดปกติ การเปลี่ยนแปลงของค่า D ส่งผลให้ราคาของ BPT (Balancer Pool Token) ในโปรโตคอล Balancer ลดลงโดยตรง แฮกเกอร์ใช้ประโยชน์จากราคา BPT ที่ตกต่ำนี้เพื่อดำเนินการอาร์บิทราจผ่านเส้นทางธุรกรรมที่ออกแบบไว้ล่วงหน้า ซึ่งท้ายที่สุดแล้วทำให้เกิดการสูญเสียสินทรัพย์จำนวนมหาศาล
ลิงก์ช่องโหว่: https://etherscan.io/tx/0x6ed07db1a9fe5c0794d44cd36081d6a6df103fab868cdd75d581e3bd23bc9742
ลิงก์โอนสินทรัพย์: https://etherscan.io/tx/0xd155207261712c35fa3d472ed1e51bfcd816e616dd4f517fa5959836f5b48569
การวิเคราะห์ทางเทคนิค
จุดเข้าโจมตี
จุดเข้าโจมตีคือ สัญญา Balancer: Vault และฟังก์ชันการเข้าที่สอดคล้องกันคือฟังก์ชัน batchSwap ซึ่งเรียก onSwap ภายในเพื่อดำเนินการสลับโทเค็น
ฟังก์ชัน onSwap(
SwapRequest หน่วยความจำ swapRequest
uint256[] การปรับสมดุลหน่วยความจำ
ดัชนี uint256 ใน
ดัชนี uint256 ออก
) การแทนที่ภายนอก onlyVault (swapRequest.poolId) ส่งคืน (uint256) {
_ก่อนSwapJoinExit();
_validateIndexes(indexIn, indexOut, _getTotalTokens());
uint256[] หน่วยความจำ scalingFactors = _scalingFactors();
กลับ
คำขอ swap.kind == IVault.SwapKind.GIVEN_IN
? _swapGivenIn(คำขอแลกเปลี่ยน, ยอดคงเหลือ, ดัชนีเข้า, ดัชนีออก, ปัจจัยการปรับขนาด)
: _swapGivenOut(swapRequest, ยอดคงเหลือ, indexIn, indexOut, scalingFactors);
-
จากพารามิเตอร์และข้อจำกัดของฟังก์ชัน เราสามารถรับข้อมูลได้หลายส่วน:
- ผู้โจมตีจำเป็นต้องเรียกใช้ฟังก์ชันนี้ผ่าน Vault พวกเขาไม่สามารถเรียกใช้โดยตรงได้
- ฟังก์ชันภายในจะเรียก
_scalingFactors()เพื่อรับปัจจัยการปรับขนาดสำหรับการดำเนินการปรับขนาด - การดำเนินการปรับขนาดได้รับการจัดการใน
_swapGivenInหรือ_swapGivenOut
การวิเคราะห์รูปแบบการโจมตี
วิธีการคำนวณราคา BPT
ในโมเดลพูลเสถียรของ Balancer ราคาของ BPT ถือเป็นจุดอ้างอิงที่สำคัญ ซึ่งกำหนดว่าผู้ใช้จะได้รับ BPT กี่รายการ และจะได้รับสินทรัพย์กี่รายการต่อ BPT
ราคา BPT = D / อุปทานรวม โดยที่ D = ค่าคงที่ จากแบบจำลอง StableSwap ของ Curve
ในการคำนวณการแลกเปลี่ยนพูล:
// StableMath._calcOut ที่ได้รับ
ฟังก์ชัน _calcOutGivenIn(
พารามิเตอร์การขยาย uint256
uint256[] การปรับสมดุลหน่วยความจำ
uint256 โทเค็นดัชนีใน
uint256 โทเค็นดัชนีออก
uint256 โทเค็นจำนวนใน
uint256 ไม่แปรเปลี่ยน
) ผลตอบแทนภายในที่บริสุทธิ์ (uint256) {
-
// outGivenIn โทเค็น x สำหรับ y - สมการพหุนามที่จะแก้ //
// ay = จำนวนเงินที่ต้องคำนวณ //
// โดย = ยอดคงเหลือโทเค็นออก //
// y = โดย - ay (finalBalanceOut) //
// D = คงที่ DD^(n+1) //
// A = ค่าสัมประสิทธิ์การขยาย y^2 + ( S + ---------- - D) * y - ------------- = 0 //
// n = จำนวนโทเค็น (A * n^n) A * n^2n * P //
// S = ผลรวมของยอดคงเหลือสุดท้าย แต่ y //
// P = ผลคูณของยอดคงเหลือสุดท้าย แต่ y //
-
// จำนวนเงินออกแล้ว เราจึงปัดเศษลงโดยรวม
balances[tokenIndexIn] = balances[tokenIndexIn].add(tokenAmountIn);
uint256 finalBalanceOut = _getTokenBalanceGivenInvariantAndAllOtherBalances(
พารามิเตอร์การขยายสัญญาณ
ยอดคงเหลือ
ไม่แปรเปลี่ยน, // ใช้ D เก่า
โทเค็นอินเด็กซ์เอาท์
-
// ไม่จำเป็นต้องใช้เลขคณิตที่ตรวจสอบแล้ว เนื่องจาก `tokenAmountIn` ถูกเพิ่มลงในยอดคงเหลือเดียวกันก่อนหน้านั้นแล้ว
// เรียก `_getTokenBalanceGivenInvariantAndAllOtherBalances` ซึ่งจะไม่เปลี่ยนแปลงอาร์เรย์ยอดคงเหลือ
balances[tokenIndexIn] = balances[tokenIndexIn] - tokenAmountIn;
คืนยอดคงเหลือ[tokenIndexOut].sub(finalBalanceOut).sub(1);
-
ส่วนที่ใช้เป็น เกณฑ์มาตรฐานสำหรับราคา BPT คือ ค่าคงที่ D กล่าวคือ การควบคุมราคา BPT จำเป็นต้องมีการควบคุม D มาวิเคราะห์กระบวนการคำนวณของ D กัน:
// StableMath._คำนวณค่าคงที่
ฟังก์ชัน _calculateInvariant(uint256 amplificationParameter, uint256[] memory balances)
ภายใน
บริสุทธิ์
ผลตอบแทน (uint256)
-
-
// ไม่แปรเปลี่ยน //
// D = ค่าคงที่ D^(n+1) //
// A = ค่าสัมประสิทธิ์การขยาย A n^n S + D = AD n^n + ----------- //
// S = ผลรวมของยอดคงเหลือ n^n P //
// P = ผลคูณของยอดคงเหลือ //
// n = จำนวนโทเค็น //
-
// ปัดลงเสมอ เพื่อให้ตรงกับเลขคณิตของ Vyper (ซึ่งจะปัดเศษเสมอ)
uint256 sum = 0; // S ในเวอร์ชัน Curve
uint256 numTokens = ยอดคงเหลือ.ความยาว;
สำหรับ (uint256 i = 0; i < numTokens; i++) {
sum = sum.add(balances[i]); // balances คือค่าที่ปรับขนาดแล้ว}
ถ้า (ผลรวม == 0) {
กลับ 0;
-
uint256 prevInvariant; // Dprev ในเวอร์ชัน Curve
uint256 invariant = sum; // D ในเวอร์ชัน Curve
uint256 ampTimesTotal = amplificationParameter * numTokens; // Ann ในเวอร์ชัน Curve
// การคำนวณแบบวนซ้ำของ D...
// การคำนวณ D ส่งผลต่อความแม่นยำของเครื่องชั่งสำหรับ (uint256 i = 0; i < 255; i++) {
uint256 D_P = ไม่แปรเปลี่ยน;
สำหรับ (uint256 j = 0; j < numTokens; j++) {
// (D_P * ไม่แปรเปลี่ยน) / (สมดุล[j] * numTokens)
D_P = Math.divDown(Math.mul(D_P, ไม่แปรเปลี่ยน), Math.mul(ยอดคงเหลือ[j], numTokens));
-
prevInvariant = ไม่แปรเปลี่ยน;
ไม่แปรเปลี่ยน = Math.divDown(
คณิตศาสตร์.mul(
// (ampTimesTotal * ผลรวม) / AMP_PRECISION + D_P * numTokens
(Math.divDown(Math.mul(ampTimesTotal, sum), _AMP_PRECISION).add(Math.mul(D_P, numTokens))),
ไม่แปรเปลี่ยน
-
// ((ampTimesTotal - _AMP_PRECISION) * ไม่แปรเปลี่ยน) / _AMP_PRECISION + (numTokens + 1) * D_P
-
Math.divDown(Math.mul((ampTimesTotal - _AMP_PRECISION), ไม่แปรเปลี่ยน), _AMP_PRECISION).add(
คณิตศาสตร์.mul((จำนวนโทเค็น + 1), D_P)
-
-
-
ถ้า (ไม่แปรเปลี่ยน > ตัวแปรก่อนหน้า) {
ถ้า (ไม่แปรเปลี่ยน - ตัวแปรก่อนหน้า <= 1) {
กลับค่าคงที่;
-
} มิฉะนั้น ถ้า prevInvariant - invariant <= 1 {
กลับค่าคงที่;
-
-
_revert(ข้อผิดพลาด.STABLE_INVARIANT_DIDNT_CONVERGE);
-
ในโค้ดด้านบน การคำนวณค่า D ขึ้นอยู่กับอาร์เรย์ของเครื่องชั่งแบบปรับสเกล ซึ่งหมายความว่าจำเป็นต้องมีการดำเนินการเพื่อเปลี่ยนความแม่นยำของเครื่องชั่งเหล่านี้ ซึ่งนำไปสู่ข้อผิดพลาดในการคำนวณค่า D
สาเหตุหลักของการสูญเสียความแม่นยำ
// BaseGeneralPool._swap ที่ได้รับ
ฟังก์ชัน _swapGivenIn(
SwapRequest หน่วยความจำ swapRequest
uint256[] การปรับสมดุลหน่วยความจำ
ดัชนี uint256 ใน
uint256 indexOut,
uint256[] ปัจจัยการปรับขนาดหน่วยความจำ
) คืนค่าเสมือนภายใน (uint256) {
// ค่าธรรมเนียมจะถูกหักออกก่อนการปรับขนาด เพื่อลดความซับซ้อนของการวิเคราะห์ทิศทางการปัดเศษ
swapRequest.amount = _subtractSwapFeeAmount(จำนวนเงิน swapRequest);
_upscaleArray(ยอดคงเหลือ, scalingFactors); // คีย์: อัปสเกลยอดคงเหลือ swapRequest.amount = _upscale(swapRequest.amount, scalingFactors[indexIn]);
uint256 amountOut = _onSwapGivenIn(swapRequest, balances, indexIn, indexOut);
// โทเค็น amountOut กำลังออกจาก Pool ดังนั้นเราจึงปัดเศษลง
ส่งคืน _downscaleDown(amountOut, scalingFactors[indexOut]);
-
การดำเนินการปรับขนาด:
// ScalingHelpers.sol
ฟังก์ชัน _upscaleArray(uint256[] จำนวนหน่วยความจำ, uint256[] ปัจจัยการปรับขนาดหน่วยความจำ) บริสุทธิ์ {
uint256 ความยาว = จำนวน.ความยาว;
InputHelpers.ensureInputLengthMatch(ความยาว, ปัจจัยการปรับขนาด.ความยาว);
สำหรับ (uint256 i = 0; i < ความยาว; ++i) {
amounts[i] = FixedPoint.mulDown(amounts[i], scalingFactors[i]); // ปัดเศษลง}
-
// จุดคงที่.mulDown
ฟังก์ชัน mulDown(uint256 a, uint256 b) คืนค่าภายในบริสุทธิ์ (uint256) {
uint256 ผลิตภัณฑ์ = a * b;
_require(a == 0 || ผลิตภัณฑ์ / a == b, Errors.MUL_OVERFLOW);
คืนผลิตภัณฑ์ / หนึ่ง; // ปัดเศษลง: ตัดทอนโดยตรง}
ดังที่แสดงไว้ด้านบน เมื่อใช้ _upscaleArray หากยอดคงเหลือมีขนาดเล็กมาก (เช่น 8-9 wei) การปัดเศษลงของ mulDown จะทำให้สูญเสียความแม่นยำอย่างมาก
รายละเอียดกระบวนการโจมตี
เฟส 1: ปรับให้เข้ากับขอบเขตการปัดเศษ
ผู้โจมตี: BPT → cbETH วัตถุประสงค์: เพื่อปรับยอดคงเหลือ cbETH ให้เป็นขอบเขตการปัดเศษ (เช่น ลงท้ายด้วย 9) สมมติว่าสถานะเริ่มต้น: ยอดคงเหลือ cbETH (เดิม): ...00000000009 wei (หลักสุดท้ายคือ 9)
ระยะที่ 2: การกระตุ้นการสูญเสียความแม่นยำ (ช่องโหว่หลัก)
ผู้โจมตี: wstETH (8 wei) → cbETH ก่อนการปรับขนาด: cbETH ยอดคงเหลือ: ...000000000009 wei อินพุต wstETH: 8 wei ดำเนินการ _upscaleArray: // การปรับมาตราส่วน cbETH: 9 * 1e18 / 1e18 = 9 // แต่ถ้าค่าจริงเป็น 9.5 ก็จะกลายเป็น 9 เนื่องจากการปัดเศษลง scaled_cbETH = floor(9.5) = 9 การสูญเสียความแม่นยำ: 0.5 / 9.5 = 5.3% การคำนวณข้อผิดพลาดสัมพัทธ์การแลกเปลี่ยน: อินพุต (wstETH): 8 wei (ปรับขนาด) ยอดคงเหลือ (cbETH): 9 (ไม่ถูกต้อง ควรเป็น 9.5) เนื่องจาก cbETH ถูกประเมินค่าต่ำเกินไป ยอดคงเหลือใหม่ที่คำนวณได้ก็จะประเมินค่าต่ำเกินไปเช่นกัน ทำให้เกิดข้อผิดพลาดในการคำนวณ D D_ต้นฉบับ = f(9.5, ...) D_ใหม่ = f(9, ...) < D_ต้นฉบับ
ระยะที่ 3: ทำกำไรจากราคา BPT ที่ตกต่ำ
ผู้โจมตี: สินทรัพย์ที่อยู่ข้างใต้ → BPT ในเวลานี้: D_new = D_original - ΔD ราคา BPT = D_new / totalSupply < D_original / totalSupply ผู้โจมตีจะได้รับ BPT จำนวนเท่าเดิมโดยมีสินทรัพย์พื้นฐานน้อยลง หรือแลกเปลี่ยนสินทรัพย์อ้างอิงเดียวกันเป็น BPT เพิ่มเติม
ผู้โจมตีข้างต้นใช้ Batch Swap เพื่อดำเนินการสลับหลายครั้งภายในธุรกรรมเดียว:
- การแลกเปลี่ยนครั้งแรก: BPT → cbETH (ปรับยอดคงเหลือ)
- การสลับครั้งที่สอง: wstETH (8) → cbETH (ทริกเกอร์การสูญเสียความแม่นยำ)
- ตลาดหลักทรัพย์ที่สาม: สินทรัพย์อ้างอิง → BPT (กำไร)
การสลับเหล่านี้จะอยู่ในธุรกรรมการสลับแบบแบตช์เดียวกันและ แบ่งปันสถานะสมดุลเดียวกัน แต่ _upscaleArray จะถูกเรียกเพื่อปรับเปลี่ยนอาร์เรย์สมดุลสำหรับการสลับแต่ละครั้ง
การขาดกลไกการโทรกลับ
กระบวนการหลักเริ่มต้นโดย Vault แล้วสิ่งนี้จะนำไปสู่การสะสมของการสูญเสียความแม่นยำได้อย่างไร คำตอบอยู่ที่ กลไกการส่งผ่านของอาร์เรย์สมดุล
// ฟังก์ชันลอจิกเมื่อ Vault เรียก onSwap: _processGeneralPoolSwapRequest(IPoolSwapStructs.SwapRequest memory request, IGeneralPool pool)
ส่วนตัว
ผลตอบแทน (จำนวนเงิน 256 ที่คำนวณ)
-
โทเค็น bytes32 ในยอดคงเหลือ;
โทเค็น bytes32 ยอดคงเหลือ;
// เราเข้าถึงดัชนีโทเค็นทั้งสองโดยไม่ต้องตรวจสอบการมีอยู่ เนื่องจากเราจะดำเนินการด้วยตนเองทันทีหลังจากนั้น
EnumerableMap.IERC20ToBytes32Map พูลเก็บข้อมูล Balances = _generalPoolsBalances[request.poolId];
uint256 indexIn = poolBalances.unchecked_indexOf(คำขอ tokenIn);
uint256 indexOut = poolBalances.unchecked_indexOf(คำขอ tokenOut);
ถ้า (indexIn == 0 || indexOut == 0) {
// โทเค็นอาจไม่ได้รับการลงทะเบียนเนื่องจากพูลเองไม่ได้ลงทะเบียน เราตรวจสอบสิ่งนี้เพื่อให้
// เหตุผลในการย้อนกลับที่แม่นยำยิ่งขึ้น
_ensureRegisteredPool(request.poolId);
_revert(ข้อผิดพลาด.TOKEN_NOT_REGISTERED);
-
// EnumerableMap จัดเก็บดัชนี *บวกหนึ่ง* เพื่อใช้ดัชนีศูนย์เป็นค่าเฝ้าระวัง - เนื่องจากค่าเหล่านี้ถูกต้อง
เราสามารถยกเลิกสิ่งนี้ได้
ดัชนีIn -= 1;
ดัชนีออก -= 1;
uint256 tokenAmount = poolBalances.length();
uint256[] หน่วยความจำ currentBalances = new uint256[](tokenAmount);
คำขอ.lastChangeBlock = 0;
สำหรับ (uint256 i = 0; i < tokenAmount; i++) {
// เนื่องจากการวนซ้ำถูกจำกัดด้วย `tokenAmount` และไม่มีโทเค็นใดที่ลงทะเบียนหรือยกเลิกการลงทะเบียนที่นี่ เราจึง
// ทราบว่า `i` เป็นดัชนีโทเค็นที่ถูกต้องและสามารถใช้ `unchecked_valueAt` เพื่อบันทึกการอ่านที่เก็บข้อมูลได้
bytes32 balance = poolBalances.unchecked_valueAt(i);
currentBalances[i] = balance.total(); // อ่านจากที่เก็บข้อมูล request.lastChangeBlock = Math.max(request.lastChangeBlock, balance.lastChangeBlock());
ถ้า (i == indexIn) {
tokenInBalance = ยอดคงเหลือ;
} มิฉะนั้นถ้า (i == indexOut) {
tokenOutBalance = ยอดคงเหลือ;
-
-
// ดำเนินการสลับ // ดำเนินการเรียกกลับคำขอสลับและคำนวณยอดคงเหลือใหม่สำหรับ 'โทเค็นเข้า' และ 'โทเค็นออก' หลังจากการสลับ
amountCalculated = pool.onSwap(คำขอ, ยอดคงเหลือปัจจุบัน, indexIn, indexOut);
(uint256 amountIn, uint256 amountOut) = _getAmounts(request.kind, request.amount, amountCalculated);
tokenInBalance = tokenInBalance.increaseCash(จำนวนเงิน);
tokenOutBalance = tokenOutBalance.decreaseCash(จำนวนเงินที่ออก);
// อัปเดตที่เก็บข้อมูล // เนื่องจากไม่มีโทเค็นที่ลงทะเบียนหรือยกเลิกการลงทะเบียนระหว่างนี้หรือเมื่อเราเรียกค้นดัชนีสำหรับ
// 'โทเค็นเข้า' และ 'โทเค็นออก' เราสามารถใช้ 'unchecked_setAt' เพื่อบันทึกการอ่านที่เก็บข้อมูล
poolBalances.unchecked_setAt(indexIn, tokenInBalance);
poolBalances.unchecked_setAt(indexOut, tokenOutBalance);
-
วิเคราะห์โค้ดด้านบน แม้ว่า Vault จะสร้างอาร์เรย์ currentBalances ใหม่ทุกครั้งที่มีการเรียก onSwap ใน Batch Swap :
- หลังจากการแลกเปลี่ยนครั้งแรก ยอดคงเหลือจะได้รับการอัปเดต (แต่ค่าที่อัปเดตอาจไม่ถูกต้องเนื่องจากการสูญเสียความแม่นยำ)
- การสลับครั้งที่สองจะดำเนินการคำนวณต่อโดยอิงจากผลลัพธ์ของการสลับครั้งแรก
- การสูญเสียความแม่นยำที่สะสมในที่สุดจะนำไปสู่การลดลงอย่างมีนัยสำคัญของค่าคงที่ D
ประเด็นสำคัญ:
// BaseGeneralPool._swap ที่ได้รับ
ฟังก์ชัน _swapGivenIn(
SwapRequest หน่วยความจำ swapRequest
uint256[] การปรับสมดุลหน่วยความจำ
ดัชนี uint256 ใน
uint256 indexOut,
uint256[] ปัจจัยการปรับขนาดหน่วยความจำ
) คืนค่าเสมือนภายใน (uint256) {
// ค่าธรรมเนียมจะถูกหักออกก่อนการปรับขนาด เพื่อลดความซับซ้อนของการวิเคราะห์ทิศทางการปัดเศษ
swapRequest.amount = _subtractSwapFeeAmount(จำนวนเงิน swapRequest);
_upscaleArray(balances, scalingFactors); // แก้ไขอาร์เรย์ในสถานที่ swapRequest.amount = _upscale(swapRequest.amount, scalingFactors[indexIn]);
uint256 amountOut = _onSwapGivenIn(swapRequest, balances, indexIn, indexOut);
// โทเค็น amountOut กำลังออกจาก Pool ดังนั้นเราจึงปัดเศษลง
ส่งคืน _downscaleDown(amountOut, scalingFactors[indexOut]);
-
// แม้ว่า Vault จะส่งผ่านอาร์เรย์ใหม่ทุกครั้ง แต่:
// 1. หากยอดคงเหลือมีขนาดเล็กมาก (8-9 wei) การสูญเสียความแม่นยำระหว่างการปรับขนาดจะมีนัยสำคัญ // 2. ในการสลับแบบแบตช์ การสลับที่ตามมาจะดำเนินการคำนวณต่อโดยอิงจากยอดคงเหลือที่สูญเสียความแม่นยำไปแล้ว // 3. ไม่มีการตรวจสอบว่าการเปลี่ยนแปลงในค่าคงที่ D อยู่ในช่วงที่เหมาะสมหรือไม่สรุป
สาเหตุของการโจมตีของ Balancer สามารถสรุปได้ดังนี้:
1. ฟังก์ชันการปรับขนาดใช้การปัดเศษลง : _upscaleArray ใช้ mulDown สำหรับการปรับขนาด ซึ่งจะทำให้เกิดการสูญเสียความแม่นยำที่สัมพันธ์กันอย่างมีนัยสำคัญเมื่อยอดคงเหลือมีขนาดเล็กมาก (เช่น 8-9 wei)
2. การคำนวณค่าคงที่มีความอ่อนไหวต่อความแม่นยำ : การคำนวณค่าคงที่ D ขึ้นอยู่กับอาร์เรย์สมดุลที่ปรับขนาด และการสูญเสียความแม่นยำจะถูกส่งต่อไปยังการคำนวณ D โดยตรง ทำให้ D มีขนาดเล็กลง
3. การขาดการตรวจสอบการเปลี่ยนแปลงในค่าคงที่ : ในระหว่างกระบวนการแลกเปลี่ยน ไม่มีการตรวจสอบว่าการเปลี่ยนแปลงในค่าคงที่ D อยู่ในช่วงที่สมเหตุสมผลหรือไม่ ซึ่งทำให้ผู้โจมตีสามารถใช้ประโยชน์จากการสูญเสียความแม่นยำเพื่อลดราคาของ BPT ได้ซ้ำแล้วซ้ำเล่า
4. การสูญเสียความแม่นยำที่สะสมในการสลับแบบแบตช์ : ในการสลับแบบแบตช์เดียวกัน การสูญเสียความแม่นยำจากการสลับหลายรายการจะสะสมและขยายกลายเป็นการสูญเสียทางการเงินจำนวนมหาศาลในที่สุด
ปัญหาสองประการนี้ ได้แก่ การสูญเสียความแม่นยำและการขาดการตรวจสอบ เมื่อรวมเข้ากับการออกแบบเงื่อนไขขอบเขตอย่างรอบคอบของผู้โจมตี ส่งผลให้เกิดการสูญเสียดังกล่าว


