Solidity: Mini Lending - 自我审计 + Slither扫描 + 漏洞修复
### 一、自我审计方法论
日期: 2026-07-01 方向: Solidity 阶段: 第四阶段:综合实战 标签: #审计 #Slither #Aderyn #安全 #MiniLending #漏洞修复
今日目标
Mini Lending 协议的代码和测试已经完成,今天进入安全审计阶段。目标:
- 手动自我审计: 按照专业审计清单逐项检查
- 自动化工具扫描: 使用 Slither 和 Aderyn 发现潜在问题
- 漏洞修复: 对每个发现的问题进行修复并验证
- 撰写审计报告: 以专业格式输出审计报告
这是从"开发者"到"安全研究员"视角的转换——在 DeFi 领域,安全审计能力是 PM 理解协议风险的核心技能。
核心概念
一、自我审计方法论
1.1 审计清单 (DeFi Lending 专用)
┌──────────────────────────────────────────────────────┐
│ Mini Lending 自我审计清单 │
├──────────────────────────────────────────────────────┤
│ │
│ [权限与访问控制] │
│ □ 1. 管理员函数是否有适当权限检查? │
│ □ 2. 是否存在单点失败的 owner 权限? │
│ □ 3. 关键参数修改是否有时间锁? │
│ □ 4. 紧急暂停机制是否存在? │
│ │
│ [重入防护] │
│ □ 5. 所有外部调用前是否已更新状态? (CEI模式) │
│ □ 6. 是否使用了 ReentrancyGuard? │
│ □ 7. 跨函数重入是否被防护? │
│ │
│ [数学与精度] │
│ □ 8. 是否存在除零风险? │
│ □ 9. 乘法是否在除法之前? (减少精度损失) │
│ □ 10. 利率计算是否正确处理了复利? │
│ □ 11. 不同 decimals 的代币是否正确处理? │
│ │
│ [预言机安全] │
│ □ 12. 预言机价格是否有过时检查? │
│ □ 13. 预言机返回0价格时是否正确处理? │
│ □ 14. 是否使用了 TWAP 而非即时价格? │
│ │
│ [清算机制] │
│ □ 15. 清算阈值设置是否合理? │
│ □ 16. 清算奖励是否足以激励清算者? │
│ □ 17. 是否存在级联清算导致的系统风险? │
│ □ 18. 自我清算是否被阻止? │
│ │
│ [代币兼容性] │
│ □ 19. 是否处理了 fee-on-transfer 代币? │
│ □ 20. 是否处理了 rebasing 代币? │
│ □ 21. 是否支持不返回 bool 的 ERC20? │
│ │
│ [闪电贷攻击] │
│ □ 22. 存取款是否在同一交易中被限制? │
│ □ 23. 价格是否可以在单笔交易中被操纵? │
│ │
│ [Gas 与 DoS] │
│ □ 24. 是否存在无界循环? │
│ □ 25. 是否存在可被堵塞的外部调用? │
│ □ 26. 批量操作是否有合理上限? │
│ │
└──────────────────────────────────────────────────────┘
1.2 审计优先级
| 优先级 | 类别 | 潜在损失 | Mini Lending 关注点 |
|---|---|---|---|
| P0 - Critical | 资金直接被盗 | 全部TVL | 重入、授权绕过、预言机操纵 |
| P1 - High | 资金间接损失 | 部分TVL | 清算逻辑错误、利率计算错误 |
| P2 - Medium | 功能异常 | 临时锁定 | DoS、Gas 超限、精度损失 |
| P3 - Low | 改进建议 | 无直接损失 | 代码风格、Gas优化、事件缺失 |
二、Slither 自动化扫描
2.1 运行 Slither
# 安装 Slither
pip install slither-analyzer
# 扫描整个项目
slither . --config-file slither.config.json
# 生成 JSON 报告
slither . --json report.json
# 只检查高严重度
slither . --filter-paths "test|lib" --exclude-informational
2.2 Slither 发现的问题与修复
发现 #1: 重入风险 (High)
Detector: reentrancy-eth
MiniLending.withdraw(address,uint256) 有重入风险:
外部调用: token.transfer(msg.sender, amount) [line 87]
状态变更在外部调用之后: userDeposits[msg.sender][token] -= amount [line 88]
// ===== 修复前 (有漏洞) =====
function withdraw(address token, uint256 amount) external {
require(userDeposits[msg.sender][token] >= amount, "Insufficient balance");
_checkHealthFactor(msg.sender);
// 危险: 先转账再更新状态 (违反 CEI)
IERC20(token).transfer(msg.sender, amount); // 外部调用
userDeposits[msg.sender][token] -= amount; // 状态更新在后
emit Withdrawn(msg.sender, token, amount);
}
// ===== 修复后 =====
function withdraw(address token, uint256 amount) external nonReentrant {
require(userDeposits[msg.sender][token] >= amount, "Insufficient balance");
// CEI: Check-Effects-Interactions
// 1. Check: 上面的 require
// 2. Effects: 先更新状态
userDeposits[msg.sender][token] -= amount;
// 检查健康因子 (在状态更新后)
_checkHealthFactor(msg.sender);
// 3. Interactions: 最后做外部调用
IERC20(token).safeTransfer(msg.sender, amount);
emit Withdrawn(msg.sender, token, amount);
}
发现 #2: 不安全的 ERC20 操作 (Medium)
Detector: unchecked-transfer
MiniLending.deposit(address,uint256): 使用了不安全的 transfer/transferFrom
某些代币不返回 bool,直接调用可能静默失败
// ===== 修复前 =====
function deposit(address token, uint256 amount) external {
// 某些代币(如 USDT)不返回 bool
IERC20(token).transferFrom(msg.sender, address(this), amount);
userDeposits[msg.sender][token] += amount;
}
// ===== 修复后 =====
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
contract MiniLending {
using SafeERC20 for IERC20;
function deposit(address token, uint256 amount) external nonReentrant {
// SafeERC20: 处理不返回 bool 的代币 + revert on failure
uint256 balanceBefore = IERC20(token).balanceOf(address(this));
IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
uint256 actualAmount = IERC20(token).balanceOf(address(this)) - balanceBefore;
// 处理 fee-on-transfer 代币: 使用实际收到的金额
userDeposits[msg.sender][token] += actualAmount;
emit Deposited(msg.sender, token, actualAmount);
}
}
发现 #3: 预言机价格过时风险 (High)
Detector: calls-loop (自定义检测)
getPrice() 没有检查价格更新时间,可能使用过时价格
// ===== 修复前 =====
function _getPrice(address token) internal view returns (uint256) {
return IPriceOracle(oracle).getPrice(token);
// 没有检查价格是否过时!
}
// ===== 修复后 =====
uint256 public constant PRICE_STALENESS_THRESHOLD = 1 hours;
function _getPrice(address token) internal view returns (uint256) {
(uint256 price, uint256 updatedAt) = IPriceOracle(oracle).getPriceWithTimestamp(token);
// 检查价格是否过时
if (block.timestamp - updatedAt > PRICE_STALENESS_THRESHOLD) {
revert StalePriceError(token, updatedAt);
}
// 检查价格是否为0
if (price == 0) {
revert ZeroPriceError(token);
}
return price;
}
发现 #4: 缺少事件 (Informational)
Detector: events-maths
关键参数修改没有发出事件,影响链下监控
// ===== 修复前 =====
function setCollateralFactor(address token, uint256 factor) external onlyOwner {
markets[token].collateralFactor = factor;
// 没有事件! 链下监控无法感知
}
// ===== 修复后 =====
event CollateralFactorUpdated(
address indexed token,
uint256 oldFactor,
uint256 newFactor
);
function setCollateralFactor(address token, uint256 factor) external onlyOwner {
require(factor <= 9000, "Factor too high"); // 最大90%
require(factor >= 1000, "Factor too low"); // 最小10%
uint256 oldFactor = markets[token].collateralFactor;
markets[token].collateralFactor = factor;
emit CollateralFactorUpdated(token, oldFactor, factor);
}
发现 #5: 清算奖励可能导致坏账 (Medium)
自我审计发现:
当抵押品价值不足以覆盖借款+清算奖励时,清算者无利可图,
可能导致无人清算 → 坏账
// ===== 修复前 =====
function liquidate(
address borrower,
address borrowToken,
uint256 repayAmount
) external {
// ...
uint256 collateralToSeize = (repayAmount * (10000 + liquidationBonus))
/ 10000 * collateralPrice;
// 如果 collateralToSeize > 剩余抵押品,交易 revert
// → 无人能清算 → 坏账!
}
// ===== 修复后 =====
function liquidate(
address borrower,
address borrowToken,
uint256 repayAmount
) external nonReentrant {
require(msg.sender != borrower, "Cannot self-liquidate");
require(getHealthFactor(borrower) < HEALTH_FACTOR_THRESHOLD, "Position is healthy");
// 计算预期获得的抵押品
uint256 collateralToSeize = _calculateSeizedCollateral(repayAmount);
// 如果抵押品不足以提供完整奖励,清算全部可用抵押品
uint256 availableCollateral = userDeposits[borrower][collateralToken];
if (collateralToSeize > availableCollateral) {
collateralToSeize = availableCollateral;
// 调整实际还款金额 (根据实际获得的抵押品反算)
repayAmount = _calculateRepayForCollateral(collateralToSeize);
}
// 执行清算
userBorrows[borrower][borrowToken] -= repayAmount;
userDeposits[borrower][collateralToken] -= collateralToSeize;
IERC20(borrowToken).safeTransferFrom(msg.sender, address(this), repayAmount);
IERC20(collateralToken).safeTransfer(msg.sender, collateralToSeize);
emit Liquidated(borrower, msg.sender, repayAmount, collateralToSeize);
}
三、Aderyn 扫描
# 安装 Aderyn (Rust编写,速度快)
cargo install aderyn
# 运行扫描
aderyn . --output report.md
Aderyn 额外发现
发现 #6: 缺少零地址检查 (Low)
// ===== 修复前 =====
constructor(address _oracle) {
oracle = _oracle; // 如果传入 address(0) 会导致后续所有价格查询失败
}
// ===== 修复后 =====
constructor(address _oracle) {
if (_oracle == address(0)) revert ZeroAddressError();
oracle = _oracle;
}
发现 #7: 浮点精度问题 (Medium)
// ===== 修复前: 精度损失 =====
function _calculateInterest(uint256 principal, uint256 timeElapsed)
internal view returns (uint256)
{
// 先除后乘 → 精度损失!
uint256 yearlyRate = principal / SECONDS_PER_YEAR * annualRate;
return yearlyRate * timeElapsed;
}
// ===== 修复后: 先乘后除 =====
function _calculateInterest(uint256 principal, uint256 timeElapsed)
internal view returns (uint256)
{
// 先乘后除,保留精度
// principal * annualRate * timeElapsed / SECONDS_PER_YEAR / RATE_PRECISION
return (principal * annualRate * timeElapsed)
/ (SECONDS_PER_YEAR * RATE_PRECISION);
}
四、完整审计报告
# Mini Lending Protocol — 自我审计报告
## 1. 审计概述
| 项目 | 详情 |
|------|------|
| **协议名称** | Mini Lending |
| **审计类型** | 自我审计 + 自动化工具 |
| **审计日期** | 2026-07-01 |
| **代码版本** | commit abc123 |
| **代码行数** | ~450 LOC (核心合约) |
| **工具** | Slither v0.10.x, Aderyn v0.2.x |
| **审计员** | 自我审计 |
## 2. 发现汇总
| 严重度 | 数量 | 已修复 |
|--------|------|--------|
| Critical | 0 | - |
| High | 2 | 2 ✅ |
| Medium | 3 | 3 ✅ |
| Low | 1 | 1 ✅ |
| Informational | 1 | 1 ✅ |
| **总计** | **7** | **7 ✅** |
## 3. 详细发现
### [H-01] 重入漏洞 — withdraw 函数
- **严重度**: High
- **位置**: MiniLending.sol#L87
- **描述**: withdraw 函数在外部调用后更新状态,违反 CEI 模式
- **影响**: 攻击者可通过恶意合约重入 withdraw,提取超额资金
- **修复**: 添加 nonReentrant modifier + 重排为 CEI 顺序
- **状态**: ✅ 已修复并测试
### [H-02] 预言机价格无过时检查
- **严重度**: High
- **位置**: MiniLending.sol#L142
- **描述**: _getPrice 未检查价格更新时间,可能使用过时价格
- **影响**: 过时价格可能导致错误的清算或超额借款
- **修复**: 添加时间戳检查 + 零价格检查
- **状态**: ✅ 已修复并测试
### [M-01] 不安全的 ERC20 操作
- **严重度**: Medium
- **位置**: MiniLending.sol 多处
- **描述**: 直接使用 transfer/transferFrom,未处理不返回bool的代币
- **影响**: USDT 等代币的转账可能静默失败
- **修复**: 使用 OpenZeppelin SafeERC20
- **状态**: ✅ 已修复
### [M-02] 清算机制坏账风险
- **严重度**: Medium
- **位置**: MiniLending.sol#L165
- **描述**: 当抵押品不足以提供清算奖励时,清算revert
- **影响**: 可能导致无人清算 → 协议积累坏账
- **修复**: 允许部分清算,动态调整清算金额
- **状态**: ✅ 已修复并测试
### [M-03] 利率计算精度损失
- **严重度**: Medium
- **位置**: MiniLending.sol#L198
- **描述**: 先除后乘导致精度损失
- **影响**: 长期累积可能导致利息计算偏差
- **修复**: 调整运算顺序为先乘后除
- **状态**: ✅ 已修复
### [L-01] 缺少零地址检查
- **严重度**: Low
- **位置**: MiniLending.sol constructor
- **描述**: 构造函数未检查关键地址参数是否为零
- **影响**: 部署错误可能导致合约不可用
- **修复**: 添加零地址检查
- **状态**: ✅ 已修复
### [I-01] 关键参数修改缺少事件
- **严重度**: Informational
- **位置**: MiniLending.sol 管理函数
- **描述**: setCollateralFactor 等函数未发出事件
- **影响**: 链下监控无法追踪参数变更
- **修复**: 添加事件 + 参数范围检查
- **状态**: ✅ 已修复
## 4. 建议 (Future Improvements)
1. **添加 Timelock**: 管理员操作添加24-48小时时间锁
2. **多预言机源**: 使用 Chainlink + Pyth 双源价格
3. **利率模型升级**: 从线性利率升级到分段利率(Kink Model)
4. **闪电贷保护**: 添加存取款时间间隔限制
5. **紧急暂停**: 添加全局暂停和单市场暂停功能
6. **形式化验证**: 使用 Certora/Halmos 对核心不变量进行证明
关键要点总结
-
审计是一种思维方式: 不是"我写的代码有什么 bug",而是"如果我是攻击者,如何利用这个合约赚钱"。这种对抗性思维是 DeFi PM 必须具备的。
-
自动化工具是起点不是终点: Slither/Aderyn 能找到80%的低级错误,但复杂的逻辑漏洞(如经济模型攻击)需要人工审计。
-
CEI 模式是第一道防线: Check-Effects-Interactions 是 Solidity 安全的基石。所有外部调用必须在状态更新之后。
-
审计报告是沟通工具: 专业的审计报告格式帮助团队理解风险、排优先级、追踪修复进度。
-
fee-on-transfer 和 rebasing 代币是常见陷阱: 不要假设所有 ERC20 都是"标准"实现。使用 balanceBefore/After 模式处理实际到账金额。
常见误区
-
误区: 用了 ReentrancyGuard 就安全了 — ReentrancyGuard 防止同一合约的重入,但跨合约重入(read-only reentrancy)需要额外防护。
-
误区: 审计工具没报问题 = 合约安全 — 工具只能检测已知模式。逻辑错误、经济攻击、治理攻击需要人工审计。
-
误区: 修了 bug 就完了 — 每个修复都需要新的测试用例验证,并且要检查修复是否引入了新问题。
-
误区: 审计一次就够了 — 每次代码变更都需要重新审计受影响的部分。CI/CD 中集成 Slither 是最佳实践。
面试关联
Q: "你如何评估一个 DeFi 协议的安全性?"
回答:
我会从四个层面评估:
- 代码审计: 是否经过知名审计公司审计?审计覆盖范围?发现了什么问题?
- 经济模型: 激励是否对齐?在极端市场条件下(如 ETH 跌50%)是否仍然稳定?
- 运维安全: 管理密钥如何管理?是否有 Timelock?紧急暂停机制?
- 历史记录: 该协议或其 fork 是否曾被攻击?如何响应的?
以我们的 Mini Lending 为例,自我审计发现了7个问题(2个 High),说明即使是简单的协议也会有安全隐患。对于生产级协议,我会要求至少2家审计公司的交叉审计 + 持续的 bug bounty。
参考资源
- Slither Documentation — Slither 官方文档
- Aderyn by Cyfrin — Aderyn 安全分析工具
- SWC Registry — 智能合约漏洞分类标准
- DeFi Security Best Practices — Trail of Bits 安全最佳实践
- Audit Report Examples — Pashov 审计报告集
- Immunefi Bug Bounty — DeFi Bug Bounty 平台