考试通知
在去中心化借贷协议的历史长河中Compound v2 的源码架构无疑是整个 DeFi 乐高世界被“分叉Fork”最多的代码资产之一。从各种小众公链上的初代借贷产品到各类结合了新兴质押衍生品LST/LRT的结构化杠杆协议其核心结算逻辑几乎都流淌着 Compound 经典双代币模型cToken 与 Comptroller的血脉。然而开源代码的繁荣背后潜藏着极其巨大的安全隐患。很多分叉团队为了支持自身独特的代币经济学往往会在不经意间对底层的核心数学库进行“微调”将老旧的 Solidity 0.5.x / 0.6.x 代码强行升级到 Solidity 0.8.x并在为了节省 Gas 的冲动下大量包裹非安全的unchecked { ... }作用域在支持非标准精度代币如 6 位精度的 USDC/USDT、8 位精度的 WBTC 与 18 位精度的 WETH时粗暴地魔改利息累加因子Borrow Index与抵押物扣押计算公式未能深刻理解抵押品清算过程中极端行情下的算术下溢Arithmetic Underflow与精度截断Precision Truncation导致健康因子计算产生荒谬的数值翻转。这种由于多代币精度不对齐和代数运算顺序错误引发的漏洞通常隐藏在数百行复杂的指数折算库中传统的静态语法扫描工具几乎无法识别。本文将手把手带你使用GLM 5.3智能合约审计模型系统还原一起典型的 Compound 分叉盘精度截断漏洞并拆解工业级的防御审计指南。一、Compound 借贷流动性评估的核心数学骨架在 Compound 的风险控制中用户的借贷安全性由审计核心合约Comptroller中的getAccountLiquidity方法全权裁决$$\text{账户净流动性 (Liquidity)} \sum \left( \text{有效抵押物价值} \times \text{抵押因子} \right) - \sum \left( \text{借出负债总价值} \right)$$有效抵押物价值将用户持有的 cToken 按照底层汇率Exchange Rate折算为原生资产再乘以预言机喂价Oracle Price负债总价值用户未偿还的借款本息总和乘以对应代币价格安全边界若 $\text{Liquidity} 0$账户健康允许继续借款或提现若 $\text{Liquidity} 0$账户发生赤字Shortfall触发抵押品被清算。二、真实致命缺陷多精度换算下的截断与下溢陷阱让我们来看一段在分叉项目中极其常见的魔改版清算抵押物扣押函数liquidateCalculateSeizeTokens// SPDX-License-Identifier: MIT pragma solidity 0.8.26; contract VulnerableCompoundForkMath { // 假设预言机统一返回 18 位精度的 USD 价格 // priceBorrowed: 被偿还负债代币的单价 (18 位精度) // priceCollateral: 被没收抵押物代币的单价 (18 位精度) // actualRepayAmount: 清算者偿还的负债代币真实数量 function calculateSeizeTokens( uint256 priceBorrowed, uint256 priceCollateral, uint256 exchangeRateCollateral, uint256 actualRepayAmount, uint8 collateralDecimals, uint8 borrowedDecimals ) public pure returns (uint256 seizeTokens) { // 开发者试图对不同代币的小数位差进行手动对齐 // 致命错误运算顺序颠倒先进行了小整数除法引发严重的精度截断归零! uint256 decimalAdjustment 10 ** (18 collateralDecimals - borrowedDecimals); // 错误实现的抵押品扣押比率: // 原意为: (repayAmount * priceBorrowed * 1.05) / priceCollateral // 但开发者在中间提前除以了 priceCollateral且未对大数进行前置放大: uint256 priceRatio (priceBorrowed * 1e18) / priceCollateral; // 灾难发生当抵押物单价极高如 WBTC $60,000而借出代币单价极低如 1 枚小币 $0.05时: // priceBorrowed * 1e18 / priceCollateral 在数值上由于向下截断结果直接变成 0! // 导致计算出的 seizeTokens 瞬间被清零! uint256 seizeAmount (actualRepayAmount * priceRatio) / decimalAdjustment; seizeTokens (seizeAmount * 1e18) / exchangeRateCollateral; } }2.1 精度截断引发的协议瘫痪与坏账穿仓在上述有缺陷的代码中当借款人借入低价垃圾代币、抵押高价值资产如 WBTC发生资不抵债时外部清算机器人尝试替其还款清算调用calculateSeizeTokens计算其应当分得的抵押物由于priceRatio计算过程中分子被分母无情碾压截断为0清算者拿到的seizeTokens变成了0清算者付出了还款成本却分不到任何抵押物回报导致全网没有任何理性套利者愿意替该用户执行清算坏账随着市场暴跌无限制失控放大最终彻底击穿借贷池本金导致全体无辜存款人资金遭遇永久挤兑封锁。三、利用 GLM 5.3 执行上下文二阶交叉审计面对此类晦涩的代数公式我们向针对以太坊 DeFi 协议微调过的GLM 5.3模型输入如下审计提示词链你是一名负责以太坊借贷协议数学安全的资深审计专家。 请深度审查以下来自 Compound v2 分叉合约的清算公式 1. 检查是否存在【先除后乘Division Before Multiplication】引发的精度截断到 0 的风险 2. 分析当借入代币价格远低于抵押品价格时如 10^-4 倍中间变量是否存在算术下溢或计算归零 3. 推演清算者在极端行情下无法获得抵押物份额导致协议坏账的攻击路径 4. 给出基于定点数大数优先相乘Multiplication Before Division的工业级加固代码。3.1 GLM 5.3 的核心审计输出GLM 5.3 展现出了极高的代数洞察力精准指出了痛点“漏洞根因在于priceRatio的独立计算违背了 EVM 定点数运算的基本准则——必须严格保持‘先乘后除’以维持最大有效精度。在被清算资产与抵押品价格跨越多个数量级时(priceBorrowed * 1e18) / priceCollateral发生了致命的向下整数截断Integer Truncation to Zero。这使得清算奖励被置零打破了借贷协议依靠清算罚金维系流动性的博弈平衡。该漏洞属于 P0 级坏账死锁风险。”四、工业级防御定点数先乘后除与全量精度对齐在 Solidity 中处理不同精度的代币折算绝对不能把中间比率拆开计算必须将所有的分子在进入除法之前统一乘以大数Scale Factor并将其合并为一个单一的大分数// SPDX-License-Identifier: MIT pragma solidity 0.8.26; contract SecureCompoundForkMath { uint256 private constant EXP_SCALE 1e18; uint256 private constant LIQUIDATION_INCENTIVE 105e16; // 1.05 代表 5% 清算奖励 error TruncationToZeroDetected(); /// notice 安全计算清算应扣押的 cToken 份额 function calculateSeizeTokensSafe( uint256 priceBorrowed, uint256 priceCollateral, uint256 exchangeRateCollateral, uint256 actualRepayAmount, uint8 collateralDecimals, uint8 borrowedDecimals ) public pure returns (uint256 seizeTokens) { // 1. 严格统一分子先乘后除将实际还款额、清算奖励因子、借出代币价格连乘 // 同时乘以前置的小数位补偿因子确保整个乘积在大数域中进行 uint256 numerator actualRepayAmount * LIQUIDATION_INCENTIVE * priceBorrowed; // 2. 统一分母将抵押物价格与基础精度连乘 uint256 denominator priceCollateral * EXP_SCALE; // 3. 补充代币精度差额的幂次补偿 if (collateralDecimals borrowedDecimals) { numerator numerator * (10 ** (collateralDecimals - borrowedDecimals)); } else { denominator denominator * (10 ** (borrowedDecimals - collateralDecimals)); } // 4. 执行单次终极除法将有效精度损失降至极限物理底线 uint256 seizeAmount numerator / denominator; if (seizeAmount 0 actualRepayAmount 0) { revert TruncationToZeroDetected(); } // 5. 按照抵押品汇率折算为最终待扣押的 cToken 数量 seizeTokens (seizeAmount * EXP_SCALE) / exchangeRateCollateral; } }五、安全审计员复核 CheckList在审查任何借贷协议的数学库时审计员必须强制核对以下四条红线先乘后除铁律Multiplication Before Division所有的代数公式中除法指令/必须且只能出现在整个计算表达式的绝对最末端严禁在中间步骤中保存未放大的除法商数多代币精度单元测试必须包含极限边界单元测试用例必须覆盖“18 位抵押品 6 位负债代币”以及“6 位抵押品 18 位负债代币”的双向场景并且输入单价必须包含从 $$0.000001$ 到 $$100,000$ 的极端价格落差非零扣押断言Non-zero Seize Check在清算函数的出口必须对算出的抵押物份额进行显式非零断言require(seizeTokens 0)绝不允许清算者在偿还债务后得到空响应警惕 unchecked 滥用在 Solidity 0.8 中除非有极其完备的形式化验证证明该减法绝对不可发生下溢否则严禁在流动性评估主流程中使用unchecked块强行跳过编译器的默认溢出防护。
GLM 5.3 审计借贷协议健康因子与抵押清算:Compound 分叉盘算术下溢漏洞挖掘
NEXT STEP
看完公告,下一步怎么走?
把报考交给靠谱的人:材料预审、批次抢报、考前辅导、复审提醒,全程有人跟。