Vitaliqu/olympix-home-assignment
GitHub: Vitaliqu/olympix-home-assignment
针对 DeFi 流动性池组合舍入漏洞的三层安全工具包,集成静态分析、有状态模糊测试与链上熔断,完整复现 Balancer V2 攻击场景。
Stars: 0 | Forks: 0
# RoundTripGuard
## 执行摘要
大多数 DeFi 漏洞利用都利用了某些明显错误的地方。Balancer V2 的舍入漏洞却并非如此。攻击流程中的每个函数在算术上都是正确的。池的不变量数学是正确的。定点库已被审计过三次。灾难源于单独正确操作的*组合* —— 这是一类现有安全工具无法捕获的故障。
RoundTripGuard 回答了一个标准审计范围从未提出过的问题:**“对该函数进行 N 次调用的序列,能否端到端地保持协议的经济不变量?”** 它实现了三个互补的层 —— 一个有状态不变量模糊测试器、一个基于 AST 的静态分析器,以及一个可选的链上熔断器 —— 每一层都从不同的角度针对同一个核心故障。
该架构旨在**超越舍入问题实现通用化**:组合不变量验证原则适用于任何安全属性是跨多个顺序调用关系的协议,而不仅仅是任何单个调用的属性。
## DeFi 漏洞利用研究 — 过去 18 个月
在深入研究 Balancer 之前,我调查了 2024 年 10 月至 2026 年 4 月期间发生的主要 DeFi 安全事件。完整的分析文章在 [`docs/exploits-overview.md`](docs/exploits-overview.md) 中。
| 漏洞利用 | 日期 | 损失 | 根本原因 | 核心教训 |
|---------|------|------|------------|------------|
| Bybit 热钱包 | 2025 年 2 月 | ~$1.46B | 供应链 / 前端注入 | 签名安全性应包括构建交易的 UI |
| Radiant Capital | 2024 年 10 月 | ~$52M | 针对性设备恶意软件 | 硬件钱包无法防范主机级别的入侵 |
| Penpie | 2024 年 9 月 | ~$27M | 跨协议重入 | 无需许可的工厂集成需要不可信调用语义 |
| UwU Lend | 2024 年 6 月 | ~$19.3M | 预言机操纵 (闪电贷) | 低迷市场的现货价格预言机极易被操纵 |
| Hedgey Finance | 2024 年 4 月 | ~$44.7M | 缺少输入验证 / 任意调用 | 用户提供的合约地址绝不应接收特权调用 |
| **Balancer V2** | **2025 年 11 月** | **~$120–128M** | **组合舍入算术** | **局部正确性 ≠ 组合安全性** |
前五个漏洞属于业界已经了解的类别。我们有针对重入、预言机操纵、输入验证和密钥管理的检查清单项。Balancer 漏洞利用则不同:它代表了一类**没有标准工具、审计检查清单项或预防措施**的故障。三年内四次胜任的审计都错过了它,因为没有任何一次审计的范围包含询问顺序操作是否端到端地保留了价值。
这个空白正是 RoundTripGuard 所要解决的。
## 为什么我选择了 Balancer V2 漏洞利用
*以下是我对这个 bug 为何如此引人注目的真实叙述。*
按美元金额计算,Bybit 黑客攻击规模更大且具有政治意义 —— 这是一起归因于国家行为体的十亿美元级盗窃案。Radiant Capital 黑客攻击是针对性供应链入侵的典范。其中任何一个都可以作为引人注目的案例研究。我选择 Balancer 是因为它是唯一一个迫使我们从根本上重新思考如何在 DeFi 协议中推理正确性的案例。
让我不断回到这个漏洞的核心洞察是我称之为**认识论失败**的东西:我们说服自己,经过验证正确的组件可以组合成一个经过验证正确的系统。这个假设是错误的,而这个 bug 就是证据。
想想四个审计团队在三年里确认了什么:`mulDown` 在算术上是正确的。`StableMath._calcInGivenOut` 正确求解了不变量方程。`divDown` 正确执行了除法。每个函数都通过了其单元测试。形式化规范验证了单次交换的不变量保留。代码库中没有单独的语句是错误的。然而,`mulDown(1, 1e12) → StableMath(0) → divDown(0)` 的组合让攻击者可以在一个区块中,以零成本彻底抽干池子。该系统同时处于局部正确和全局灾难性崩溃的状态。
特别令人不安的是,这种故障模式*并不罕见*。它不需要闪电贷、治理攻击或受损的密钥。它只需要: 将储备金定位在攻击者在链下计算出的阈值之下,以及 用一个小的整数参数调用一个公共函数,在一个批量交易中重复 65 次。其复杂性完全在于认识到该阈值的存在。一旦你理解了当 `a × b < 1e18` 时 `mulDown(a, b) = 0`,其余的就会自然而然地发生。
更深层的含义在于**当前安全工具的局限性**。如今应用于 DeFi 的形式化验证非常擅长证明关于单个交易的不变量。Certora 可以验证单次交换保持了池的不变量。但它很难轻松表达的是:“对于任何交换序列,协议收集的价值是否至少与其分发的价值一样多?” 这是一个*时序*或*顺序*属性,它需要一种不同类型的工具 —— 有状态模糊测试、带有幽灵变量的基于属性的测试,或显式的顺序不变量规范。
这也是了解**利率增强型 AMM 更广泛系统性风险**的窗口。随着越来越多的生息代币 (wstETH, rETH, cbETH, weETH) 成为 DeFi 基础设施,越来越多的协议将在其核心定价路径中组合按利率缩放的定点算术。如果每个协议在选择语义上应使用 `mulUp` 的地方使用了 `mulDown`,它就会继承这一整类漏洞。Balancer bug 并非孤例;它是行业广泛采用但未完全理解其属性的设计模式的一个症状。
最后,修复只是一个单字更改 —— 在一个调用点将 `mulDown` 改为 `mulUp`。实施修复所花的时间比阅读错过该 bug 的四份审计报告中的任何一份所花的时间都要少。修复复杂度与发现复杂度之间的这种不成比例,对我来说,是整个事件中最重要的事实。这意味着更好的工具 —— 而不是更多的审计 —— 才是正确的投资。
这就是 RoundTripGuard 背后的论点。
## 架构
### 两个主要工具,一个可选后盾
对于控制自身部署的任何协议,两个工具就足够了。
**工具 1 — 有状态不变量模糊测试器** (`test/RoundTripInvariantTest.t.sol`)
幽灵变量 `ghost_totalIn` 和 `ghost_totalOut` 在整个模糊测试序列中累积价值。一旦有任何交换以零成本提取价值,不变量 `ghost_totalOut ≤ ghost_totalIn` 就会触发。运行时需将储备金初始化为 `ceil(1e18 / scalingFactor) + 1` —— 即截断可能发生的精确阈值。
**工具 2 — AST 舍入分类器** (`cli/scaling-audit.ts`)
将 Solidity 源码解析为 AST,并将交换函数中的 `mulDown → divDown` 对标记为 CRITICAL。以退出代码 1 退出,阻止 PR 合并。无需 Foundry、无需 Anvil、无需环境 —— 在任何 CI 运行器上 5 秒内即可运行。
**工具 3 — 鎖上熔斷器** (`src/InvariantMonitor.sol` + `src/EmergencyPauser.sol`) — 可选
适用于*已经部署*且无法在不经过治理流程的情况下重新部署的协议。无许可监控发出 `CircuitBreakerTripped` 事件;一个单独授权的 `EmergencyPauser` 持有 `pause()` 权限 —— 这种分离防止了恶意监控器触发错误暂停。仅在 TVL > $10M 的池中添加此项;低于此值时,Gas 开销和错误暂停风险将超过其收益。
### 削减了什么及其原因
**链下 WebSocket 扫描器** 已从项目中完全移除,原因有三:
1. **冗余信号。** 它检测到与链上 `InvariantMonitor` 相同的不变量偏移。具有相同信号的两个检测器增加的是操作复杂性,而不是安全性。
2. **无法影响原子交易。** 真正的 Balancer 攻击在一个 `batchSwap` 中完成了所有 65 次交换。当 WebSocket 事件触发并将警报路由给工程师时,交易早已敲定。链下监控在结构上太慢,无法阻止此类攻击。
3. **没有独特优势的运营成本。** 运行实时 WebSocket 订阅者、路由警报和维护待命响应需要耗费真实的工程时间。如果你已经拥有连接了 Keeper 的工具 3,扫描器会在不增加覆盖范围的情况下重复覆盖。
### 为什么两个工具就足够了
这类 bug 有两个必须同时满足的必要条件:
1. **交换函数中存在 `mulDown → divDown` 序列。** 工具 2 在每个 PR 上静态捕获此情况。
2. **池储备金可以被压低至 `ceil(1e18 / scalingFactor)` 以下。** 工具 1 动态捕获此情况 —— 在次阈值储备金下任何 `amountIn = 0` 都会在第一次模糊调用时触发幽灵变量不变量。
如果条件 (1) 不存在,代码库中就不会存在截断为零的组合。如果条件 (1) 存在,则条件 (2) 在机械原理上是可测试的。要使漏洞利用起作用,这两个条件必须同时成立;工具 2 在 CI 中阻断了 (1),工具 1 在部署前发现了 (2)。它们共同覆盖了此类 bug 的完整攻击面。
剩余的空白:跨越函数边界的舍入组合 —— 即一个函数中的 `mulDown` 将其结果传递给包含 `divDown` 的另一个单独函数。工具 2 的污点跟踪仅限函数内部;不跟踪跨文件数据流。工具 1 现在扫掠六个缩放因子速率 (`RoundTripRateSweepTest`) 并测试跨池顺序抽干 (`MultiPoolInvariantTest`),从而关闭了速率扫掠的空白。剩余的两个局限性已记录在局限性中。
## 为什么不只是修复数学问题?
修复只是一个字:在 `MockVulnerablePool.sol` 的第 64 行将 `mulDown` 改为 `mulUp`。如果修复如此简单,为什么要围绕它构建工具?
**发现 bug 才是困难的部分。** 三年内四个胜任的审计团队审查了这段代码,都没有标记出那一行。不是因为他们粗心 —— 而是因为单个调用点上的舍入方向只有在上下文中才是错误的:当你知道这是 GIVEN_OUT 交换的放大步骤,其结果输入到 StableMath 的零检查,并且在次阈值储备金下每次交换的净效应是零摊销成本时。孤立地看 `mulDown(amountOut, SCALING_FACTOR)`,所有这些都不可见。
**协议不是静态的。** Balancer V2 已经部署了 47 种池变体、多种费率提供者配置以及跨越三年的升级历史。每种新的池类型和每次 `_upscale` 覆盖都是重新引入同类 bug 的绝佳机会。一次性修复不能防止回归。而 CI 门禁可以。
**该模式具有通用性。** 定点定价路径中的 `mulDown → divDown` 出现在 Curve、Uniswap V3 价格计算、Aave 利息累积和 Compound 汇率中。这不是 Balancer 特有的 bug;它是任何定点 AMM 在开发期间都可能引入的一类错误。
### RoundTripGuard 与现有工具的比较
| 工具 | 检查组合序列 | 检测舍入方向 | CI 就绪 | 成本 |
|------|-------------------------------|---------------------------|----------|------|
| Slither | 否 | 否 | 是 (快速) | 免费 |
| Mythril | 部分 (有界) | 否 | 慢 (~小时) | 免费 |
| Echidna | 是 (有状态) | 需要自定义不变量 | 是 | 免费 |
| Certora Prover | 仅限单次交易 | 是,如果已指定 | 是 | $$$ |
| 人工审计 | 取决于范围 | 有时 | 否 | $$$$ |
| **RoundTripGuard L1** | **是** | **幽灵变量不变量** | **是** | **免费** |
| **RoundTripGuard L2** | **不适用 (语法)** | **是,无需不变量** | **是 (<5秒)** | **免费** |
**Echidna 留下的空白:** 如果使用了正确的不变量,Echidna 本可以捕获此 bug。`ghost_totalOut <= ghost_totalIn` 并不是一个默认不变量 —— 你需要知道要去编写它。Layer 1b 提供了这个不变量作为起点。Layer 2 则在无需用户已经怀疑存在舍入问题的情况下,揭示了语法前提条件。
**Certora 留下的空白:** Certora 的证明是成立的 —— 单次交换不变量保留为真。缺失的规范是 `∀ swap 序列: Σ(amountIn) ≥ Σ(amountOut)`。在无界调用序列上的顺序价值保留,在审计时超出了 DeFi 标准 Certora 规范模式的范围。
## 在真实协议中的集成
### CI 中的 Layer 2 — 静态分析 (Day 0)
添加到您的安全工作流程中:
```
# .github/workflows/security.yml
- name: Rounding audit
run: npx ts-node cli/scaling-audit.ts --file src/
# exits 1 on CRITICAL findings, blocks merge
```
不到 5 秒,无需 Foundry,无需环境设置。每个 AMM 定价代码的 PR 都会被自动检查。误报 (匹配启发式的非交换函数) 只需一行抑制注释即可消除。
**谁设置它:** DevOps 或安全工程师,一次性设置。
**何时触发:** 在每个修改任何 `.sol` 文件的 PR 上。
**信号:** 退出代码 1 + CRITICAL 报告阻止合并。
### 部署前 Layer 1 — 有状态模糊测试器 (主网之前)
作为部署前安全检查清单的一部分运行,在代码审查之后、部署之前。模糊测试器必须在低于正常值的储备金下初始化,以触发截断边界。对于 `scalingFactor = X` 的池,截断阈值为 `ceil(1e18 / X)` —— 将储备金初始化为 `threshold + 1`。
```
forge test --match-contract RoundTripInvariantTest --fuzz-runs 10000
```
**谁运行它:** 安全审查员 (内部或外部)。每种池类型一次性运行。
**何时运行:** 代码冻结后,部署前。
**信号:** 任何带有非零幽灵变量的 `FAIL` 都是一个阻断性发现。
### 部署后 Layer 3 — 熔断器 (仅限高 TVL 池)
为 TVL > $10M 且无法在没有治理过程的情况下暂停和重新部署的池部署 `InvariantMonitor` + `EmergencyPauser`。低于此阈值,误报暂停 (流动性中断、Keeper Gas、声誉成本) 的成本可能会超过受保护的预期价值。
**谁部署它:** 协议安全团队。
**何时部署:** 与池一起,在流动性的第 1 天即激活。
**信号:** `CircuitBreakerTripped` 事件 → Keeper 调用 `EmergencyPauser.pause(poolId)` → 协议在一个区块内收到通知。
Keeper 应由去中心化自动化服务 (Chainlink Automation, Gelato) 提供支持。使用 `addKeeper(address)` 将多个 EOA 列入白名单 —— `EmergencyPauser` Keeper 白名单消除了单点故障风险;详情请参阅关键改进。
## 概念验证
PoC 位于 [`roundtripguard/`](roundtripguard/) 中。它包含:
| 文件 | 它演示了什么 |
|------|---------------------|
| `src/MockVulnerablePool.sol` | 带有 bug 的 AMM:对 `amountOut` 进行 `mulDown` 放大 |
| `src/MockFixedPool.sol` | 带有修复的 AMM:向上取整除法,攻击者总是多付 |
| `src/FixedPoint.sol` | Balancer `FixedPoint.sol` 的镜像 —— 相同算法,相同名称 |
| `src/InvariantMonitor.sol` | 无许可链上哨兵 — Layer 3 检测器 |
| `src/EmergencyPauser.sol` | 授权暂停执行者 — Layer 3 执行者 |
| `test/BalancerAttackReplay.t.sol` | Layer 1a: 确定性攻击 + 修复验证 |
| `test/RoundTripInvariantTest.t.sol` | Layer 1b: 有状态模糊测试器,在易受攻击的池上失败 |
| `test/RoundTripHandler.sol` | 带有模糊测试器幽灵变量的 Handler |
| `test/MonitorBlocks.t.sol` | Layer 1c: 完整的从检测到暂停的集成测试 |
| `test/EdgeCases.t.sol` | 54 个针对算术、监控器和暂停器边界情况的单元测试 |
| `cli/scaling-audit.ts` | Layer 2: 基于 AST 的舍入检测器,CI 就绪 |
Next.js Web 演示位于 [`demo/`](demo/) 中 —— 易受攻击池与受保护池的并排动画比较。
### 先决条件
```
# Foundry (Solidity 编译器 + 测试运行器 + Anvil 本地链)
curl -L https://foundry.paradigm.xyz | bash && foundryup
# Node.js 18+ (用于 CLI 工具, Next.js 演示)
# https://nodejs.org
```
### 快速开始
```
# 1. 构建合约
cd roundtripguard && forge build
# 2. Layer 1a — 攻击 PoC (确定性)
# 预期:漏洞池被抽空,修复后的池保持不变
forge test --match-contract BalancerAttackReplay -vvv
# 3. Layer 1b — 有状态不变性模糊测试器
# 预期:FAIL on RoundTripInvariantTest (在第一次调用时捕获错误)
forge test --match-contract RoundTripInvariantTest -vvv
# 4. Layer 1c — 熔断器集成
# 预期:在 swap 2 时跳闸,keeper 暂停,swap 3 revert BAL#211
forge test --match-contract MonitorBlocks -vvv
# 5. Layer 2 — 静态分析 (无需 node,无需 Anvil)
npx ts-node cli/scaling-audit.ts --file src/MockVulnerablePool.sol # → CRITICAL, exit 1
npx ts-node cli/scaling-audit.ts --file src/MockFixedPool.sol # → clean, exit 0
# — 或直接从 repo 根目录运行所有内容 —
make demo # full end-to-end demo script
make poc # Layer 1a only
make fuzz # Layer 1b only
make audit # Layer 2 only
```
### 预期输出 — Layer 1b 模糊测试器
```
[FAIL: FREE_EXTRACTION_DETECTED: 1 zero-cost swap(s) across 1 total: 1 != 0]
[Sequence]
swapMicro() — amountIn=0, amountOut=1 (first call catches the bug)
[PASS] invariant_fixedPoolNoProfit() (runs: 500, calls: 10000, reverts: 234)
[PASS] invariant_fixedPoolNoFreeExtraction() (runs: 500, calls: 10000, reverts: 234)
```
### 预期输出 — Layer 2 静态分析
```
# ScalingAudit v2 报告
## 文件:src/MockVulnerablePool.sol
🔴 **[CRITICAL]** in `swapGivenOut`
- Upscale: `mulDown` (line 64)
- Downscale: `divDown` (line 71)
- mulDown (upscale) + divDown (downscale): both round toward zero.
At balance < 1e6 / SCALING_FACTOR wei, upscaled amountOut rounds to 0
→ scaledIn = 0 → amountIn = 0 (free extraction). Pool drained in O(reserve) steps.
Exit code: 1 (CRITICAL findings)
```
### 实时 Web 演示 (已部署)
**[在线体验 → https://olympix-home-assignment.vercel.app/](https://olympix-home-assignment.vercel.app/)**
该演示完全在浏览器中运行,并实时重播完整的 **65 次交换攻击序列**:
- **左侧面板** — 易受攻击的 `ComposableStablePool` 被抽干至零
- **右侧面板** — 受 RoundTripGuard 保护的池 (熔断器在第 2 次交换时触发)
无需本地设置,无需 Anvil,无需 `npm install`。只需打开并观看漏洞利用与防护的并排展示。
## 局限性与权衡
### Layer 2 — 静态分析 (`scaling-audit.ts`)
分析器使用函数内污点跟踪,能可靠地检测交换函数内部的 `mulDown` → `divDown` 模式。然而,仍存在以下局限性:
- 未实现跨文件和跨函数的数据流分析。污点传播目前仅限于单个函数体。
- 检测依赖于可配置的语法模式,对于高度非标准的代码库可能需要额外调整。
### Layer 1 — 有状态不变量模糊测试器
模糊测试器通过幽灵变量有效识别零成本提取和往返获利违规行为。它已扩展支持速率扫掠和多池功能。其有效性仍取决于适当的初始条件和缩放因子范围,以揭示边界情况漏洞。
### Layer 3 — 链上熔断器 (`InvariantMonitor`)
该层作为已部署的高 TVL 池的**可选部署后后盾**提供。其主要局限性在于:
- 它无法阻止在单个交易内执行的原子攻击 (例如原始 Balancer `batchSwap`)。必须在部署前通过 Layer 1 和 Layer 2 阻止此类漏洞利用。
- 监控器引入了显著的 Gas 开销 (在正常路径下 `checkAfterSwap` 大约消耗 82,000 Gas)。
### Gas 成本
在当前实现中,`InvariantMonitor.checkAfterSwap` 平均消耗 **~82,000 Gas**。这对于高频池意味着交易成本的实质性增加。
因此,建议仅在 TVL 巨大 (>$10M) 的池中使用 Layer 3,此时额外的安全收益证明其开销是值得的。对于大多数协议,修正数学 (Layer 2) 和严格的部署前模糊测试 (Layer 1) 的组合可以在没有运行时开销的情况下提供充分的保护。
### 与真实 Balancer 池的集成阻力
`InvariantMonitor.sol` 包含一个被注释掉的 `IBalancerVault` 适配器块,包含三个具体的适配步骤:
1. 将 `pool.getLastInvariant()` 替换为 `vault.getPoolTokens(poolId)`
2. 通过 Newton-Raphson 计算 StableSwap 不变量 D (参考:Balancer 的 `StableMath.sol:_calculateInvariant`)
3. 在计算 D 之前,通过其费率提供者标准化每个代币余额
完整的实现 (链上 Newton-Raphson、多代币费率提供者读取) 仍超出范围,以保持安全逻辑在单独阅读时的可读性。
### 不涵盖的内容
RoundTripGuard 的范围限定于组合算术不变量。它不能捕获预言机操纵、重入、密钥泄露、缺少输入验证或上述研究调查中的任何其他漏洞类别。它是更广泛安全堆栈中的一层,而不是替代品。
## 经验教训
**1. “这个函数正确吗?”这个问题是不够的。**
正确的问题是“这个函数被顺序调用 N 次,能否保持协议的经济不变量?”这是一个组合属性。它需要组合工具。
**2. 审计范围是一个风险面。**
当多个审计团队各自声明其范围超出组合分析边界时,组合将永远不会被分析。对于任何处理代币转移的函数,审计范围必须明确包含多调用序列。
**3. 形式化验证证明的是你指定的内容,而不是你需要的内容。**
Certora 的证明是正确的:它证明了单次交换不变量保留,这是成立的。缺失的规范是顺序价值保留,而这并不成立。形式化工具的功能仅限于赋予它们的属性。
**4. 舍入方向是一个安全关键的设计决策。**
在 GIVEN_OUT 交换管道中,必须根据协议的负债来选择每个舍入方向,而不仅仅是为了算术上的便利。一条解释*为什么*选择每种舍入方向的注释,本可以在第一次代码审查时就让 bug 暴露无遗。
**5. 由攻击者创造的低流动性状态仍然是有效的操作条件。**
任何可通过合法操作达到的状态都是有效的攻击前提。协议应在极端参数值 (1 wei、2 wei、8 wei) 下进行模糊测试 —— 而不仅仅是在“现实”的操作范围内。
**6. 修复总是比发现简单。**
`mulDown` 改为 `mulUp`。一个字而已。修复复杂性 (微不足道) 与发现复杂性 (攻击者需要进行 1000 亿次模拟,防御者花费了 3 年时间和 4 次审计) 之间的不对称性,正是投资于更好的工具而不是更多审查时间的理由。
## 延伸阅读
- [`docs/balancer-exploit-deep-dive.md`](docs/balancer-exploit-deep-dive.md) — 1,100 行技术参考:完整词汇表、漏洞利用机制、逐文件代码演练、攻防比较
- [`docs/exploits-overview.md`](docs/exploits-overview.md) — 研究调查中所有六个漏洞的详细分析文章
- [`roundtripguard/`](roundtripguard/) — 所有源代码、测试、CLI 和监控器
## 仓库结构
```
.
├── roundtripguard/ # Core security toolkit
│ ├── src/
│ │ ├── FixedPoint.sol # Fixed-point math library (mirrors Balancer)
│ │ ├── MockVulnerablePool.sol # Pool with the rounding bug
│ │ ├── MockFixedPool.sol # Pool with the fix
│ │ ├── InvariantMonitor.sol # Layer 3: on-chain sentinel
│ │ └── EmergencyPauser.sol # Layer 3: authorized enforcer
│ ├── test/
│ │ ├── BalancerAttackReplay.t.sol # Layer 1a: deterministic PoC
│ │ ├── RoundTripInvariantTest.t.sol # Layer 1b: stateful fuzzer + rate sweep + multi-pool
│ │ ├── RoundTripHandler.sol # Single-pool fuzzer handler + ghost variables
│ │ ├── MultiPoolHandler.sol # Cross-pool sequential drain handler
│ │ ├── MonitorBlocks.t.sol # Layer 1c: circuit breaker integration test
│ │ └── EdgeCases.t.sol # 54 unit tests (arithmetic, monitor, pauser)
│ └── cli/
│ ├── scaling-audit.ts # Layer 2: CLI entry point
│ ├── ast-walker.ts # Solidity AST traversal
│ ├── rules.ts # Rounding pair classification
│ ├── reporter.ts # Markdown + JSON output
│ └── fetcher.ts # Etherscan + local file loader
├── demo/ # Next.js live exploit visualizer
│ ├── app/ # App Router pages + API routes
│ ├── components/ # React UI components
│ └── lib/ # Types, ABIs, deploy singleton
├── docs/
│ ├── balancer-exploit-deep-dive.md # Full technical reference
│ └── exploits-overview.md # Multi-exploit research survey
├── demo.sh # End-to-end demo script
├── Makefile # Convenience targets
└── .env.example # Environment variable template
```
标签:AST分析器, Balancer V2, CISA项目, DeFi安全, DeFi攻击防御, MITM代理, Web3安全, 不变量测试, 云安全监控, 加密货币, 区块链安全, 去中心化金融, 安全工具包, 形式化验证, 攻击重放, 智能合约审计, 流动性池, 漏洞复现, 状态模糊测试, 组合舍入漏洞, 链上断路器, 静态分析