web3-audit

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

WEB3 SMART CONTRACT AUDIT

WEB3 智能合约审计

10 bug classes. Pre-dive kill signals. Foundry PoC template. Real paid examples.

10类漏洞、审计前筛选排除信号、Foundry PoC模板、真实赏金案例。

PRE-DIVE KILL SIGNALS (check BEFORE any code review)

审计前筛选排除信号(代码审查前必查)

ZKsync lesson: $322M TVL + OZ audit + 750K LOC + 5 sessions = 0 findings. Large well-audited bridges are extremely hard.
  1. TVL < $500K → max payout capped too low for effort
  2. 2+ top-tier audits (Halborn, ToB, Cyfrin, OpenZeppelin) on simple protocol → bugs already found
  3. Protocol < 500 lines, single A→B→C flow → minimal attack surface
  4. Formula:
    max_realistic_payout = min(10% × TVL, program_cap)
    — if < $10K, skip
Soft kill: OZ/ToB/Cyfrin audit on current version + codebase > 500K LOC → expect 40+ hours for maybe 1 finding. Only proceed if bounty floor > $50K AND you have protocol-specific expertise.
Target scoring (go if >= 6/10):
  • TVL > $10M: +2
  • Immunefi program with Critical >= $50K: +2
  • No top-tier audit on current version: +2
  • < 30 days since deploy: +1
  • Protocol you've hunted before: +1
  • Source code + natspec comments: +1
  • Upgradeable proxies: +1

ZKsync教训:3.22亿美元TVL + OpenZeppelin审计 + 75万行代码 + 5轮审计 = 0漏洞发现。大型且经过严格审计的桥接协议极难找到漏洞。
  1. TVL < 50万美元 → 最高赏金上限过低,投入产出比不足
  2. 2次及以上顶级审计(Halborn、ToB、Cyfrin、OpenZeppelin)的简单协议 → 漏洞已被发现
  3. 协议代码少于500行,仅单一A→B→C流程 → 攻击面极小
  4. 计算公式
    实际最高可获赏金 = min(10% × TVL, 项目赏金上限)
    — 若低于1万美元,直接跳过
软性排除条件:当前版本经过OpenZeppelin/ToB/Cyfrin审计 + 代码量超过50万行 → 需投入40小时以上才可能找到1个漏洞。仅当赏金下限高于5万美元且你具备该协议领域的专业知识时,才继续推进。
目标评分(≥6/10则值得投入)
  • TVL > 1000万美元:+2
  • Immunefi项目Critical级漏洞赏金≥5万美元:+2
  • 当前版本未经过顶级审计:+2
  • 部署时间<30天:+1
  • 曾挖掘过该协议:+1
  • 提供源代码+natspec注释:+1
  • 可升级代理:+1

THE ONE RULE

核心规则

"Read ALL sibling functions. If
vote()
has a modifier, check
poke()
,
reset()
,
harvest()
. The missing modifier on the sibling IS the bug."
This single rule explains 19% of all Critical findings.

"阅读所有关联函数。如果
vote()
有修饰器,务必检查
poke()
reset()
harvest()
。关联函数缺失修饰器本身就是漏洞。"
这条规则解释了19%的Critical级漏洞发现。

1. ACCOUNTING STATE DESYNCHRONIZATION

1. 账目状态不一致

#1 Critical bug class — 28% of all Criticals on Immunefi.
排名第1的Critical级漏洞类别 — 占Immunefi平台所有Critical级漏洞的28%。

What It Is

漏洞定义

Two state variables supposed to stay in sync. One code path updates A but forgets B. Later code reads both and makes decisions based on stale B.
Real Value = A - B
If A updated but B isn't → Real Value appears larger → phantom value
两个本应保持同步的状态变量,某条代码路径更新了变量A却忘记更新变量B。后续代码读取这两个变量时,会基于过时的B做出错误决策。
实际价值 = A - B
若A更新但B未更新 → 实际价值被高估 → 产生虚假价值

Root Cause Patterns

根源模式

Variant 1: Phantom Yield (Yeet protocol — 35 duplicate reports)
solidity
function startUnstake(uint256 amount) external {
    totalSupply -= amount;  // decremented BEFORE transfer
    // aToken.balanceOf(this) still reflects old value
    // yieldAmount = aToken.balanceOf - totalSupply = phantom yield
}
Variant 2: Fast Path Skips State Update (Alchemix V3)
solidity
function claimRedemption(uint256 tokenId) external {
    if (transmuter.balance >= amount) {
        transmuter.transfer(user, amount);
        _burn(tokenId);
        return;  // EARLY RETURN — cumulativeEarmarked, _redemptionWeight, totalDebt never updated
    }
    // Slow path: updates all state vars correctly
    alchemist.redeem(...);
}
Variant 3: Update Happens in Wrong Order (Alchemix)
solidity
function deposit(uint256 amount) external {
    _shares = (amount * totalShares) / totalAssets;  // calculated BEFORE deposit
    totalAssets += amount;   // assets added AFTER shares calculated → wrong rate
}
变体1:虚假收益(Yeet协议 — 35份重复报告)
solidity
function startUnstake(uint256 amount) external {
    totalSupply -= amount;  // 转账前先减少总供应量
    // aToken.balanceOf(this)仍反映旧值
    // 收益金额 = aToken.balanceOf - totalSupply = 虚假收益
}
变体2:快速路径跳过状态更新(Alchemix V3)
solidity
function claimRedemption(uint256 tokenId) external {
    if (transmuter.balance >= amount) {
        transmuter.transfer(user, amount);
        _burn(tokenId);
        return;  // 提前返回 — cumulativeEarmarked、_redemptionWeight、totalDebt从未更新
    }
    // 慢速路径:正确更新所有状态变量
    alchemist.redeem(...);
}
变体3:更新顺序错误(Alchemix)
solidity
function deposit(uint256 amount) external {
    _shares = (amount * totalShares) / totalAssets;  // 存款前先计算份额
    totalAssets += amount;   // 资产添加晚于份额计算 → 汇率错误
}

Grep Patterns

Grep检索模式

bash
undefined
bash
undefined

Find all accounting variables

查找所有账目相关变量

grep -rn "totalSupply|totalShares|totalAssets|totalDebt|cumulativeReward|rewardPerShare" contracts/
grep -rn "totalSupply|totalShares|totalAssets|totalDebt|cumulativeReward|rewardPerShare" contracts/

Find all early returns in claim/redeem functions

查找claim/redeem函数中的所有提前返回语句

grep -rn "\breturn\b" contracts/ -B3 | grep -B3 "if\b"
grep -rn "\breturn\b" contracts/ -B3 | grep -B3 "if\b"

For each early return: which state updates in normal path are skipped?

针对每个提前返回:检查正常路径中的哪些状态更新被跳过?


---

---

2. ACCESS CONTROL

2. 访问控制

#2 Critical — 19% of Criticals. $953M lost in 2024 alone.
排名第2的Critical级漏洞 — 占Critical级漏洞的19%。仅2024年就因这类漏洞损失9.53亿美元。

Variant 1: Missing Modifier on Sibling Function

变体1:关联函数缺失修饰器

solidity
function vote(uint256 tokenId) external onlyNewEpoch(tokenId) {  // guarded
function reset(uint256 tokenId) external onlyNewEpoch(tokenId) { // guarded
function poke(uint256 tokenId) external {                         // NO GUARD → infinite FLUX inflation
}
solidity
function vote(uint256 tokenId) external onlyNewEpoch(tokenId) {  // 有防护
function reset(uint256 tokenId) external onlyNewEpoch(tokenId) { // 有防护
function poke(uint256 tokenId) external {                         // 无防护 → 无限FLUX通胀
}

Variant 2: Wrong Check (Existence vs Ownership)

变体2:错误校验(存在性 vs 所有权)

solidity
function split(uint256 tokenId, uint256 amount) external {
    _requireOwned(tokenId);  // checks if token EXISTS, not if caller OWNS it
    _burn(tokenId);
    _mint(msg.sender, amount);  // attacker steals tokens they don't own
}
solidity
function split(uint256 tokenId, uint256 amount) external {
    _requireOwned(tokenId);  // 检查代币是否存在,而非调用者是否拥有代币
    _burn(tokenId);
    _mint(msg.sender, amount);  // 攻击者窃取不属于自己的代币
}

Variant 3: Silent Modifier (if vs require)

变体3:静默修饰器(if vs require)

solidity
// VULNERABLE — non-admin silently gets through:
modifier onlyAdmin() {
    if (msg.sender == admin) {
        _;  // body only executes for admin, but non-admin doesn't revert
    }
}
// CORRECT: require(msg.sender == admin, "Not admin"); _;
solidity
// 存在漏洞 — 非管理员可静默通过:
modifier onlyAdmin() {
    if (msg.sender == admin) {
        _;  // 仅管理员可执行函数体,但非管理员不会触发回滚
    }
}
// 正确写法:require(msg.sender == admin, "Not admin"); _;

Variant 4: Uninitialized Proxy

变体4:未初始化代理

solidity
function initialize(address _owner) public {  // MISSING: initializer modifier
    owner = _owner;  // anyone can call → become owner
}
// Fix: constructor() { _disableInitializers(); }
solidity
function initialize(address _owner) public {  // 缺失:initializer修饰器
    owner = _owner;  // 任何人都可调用 → 成为所有者
}
// 修复方案:constructor() { _disableInitializers(); }

Grep Patterns

Grep检索模式

bash
undefined
bash
undefined

Find sibling function families — do ALL have the same modifier set?

查找关联函数组 — 是否所有函数都有相同的修饰器集合?

grep -rn "function vote|function poke|function reset|function update|function claim|function harvest" contracts/ -A2
grep -rn "function vote|function poke|function reset|function update|function claim|function harvest" contracts/ -A2

Ownership check: existence vs ownership?

所有权校验:是存在性还是所有权?

grep -rn "_requireOwned|ownerOf|_isApprovedOrOwner|_checkAuthorized" contracts/ -B5
grep -rn "_requireOwned|ownerOf|_isApprovedOrOwner|_checkAuthorized" contracts/ -B5

Silent modifiers

静默修饰器

grep -rn "modifier\b" contracts/ -A8 | grep -B3 "if (" | grep -v "require|revert"
grep -rn "modifier\b" contracts/ -A8 | grep -B3 "if (" | grep -v "require|revert"

Uninitialized initializer

未初始化的initialize函数

grep -rn "function initialize\b" contracts/ -A3 grep -rn "_disableInitializers()" contracts/
undefined
grep -rn "function initialize\b" contracts/ -A3 grep -rn "_disableInitializers()" contracts/
undefined

Real Paid Examples

真实赏金案例

ProtocolPayoutBug
Wormhole$10MUninitialized UUPS proxy → anyone calls initialize()
ZeroLendn/asplit() uses existence check, not ownership check
Alchemixn/apoke() missing onlyNewEpoch → infinite FLUX inflation
Parity$150M frozenNo access control on initWallet() in library

协议赏金漏洞类型
Wormhole1000万美元未初始化的UUPS代理 → 任何人都可调用initialize()
ZeroLend未公开split()使用存在性校验而非所有权校验
Alchemix未公开poke()缺失onlyNewEpoch修饰器 → 无限FLUX通胀
Parity1.5亿美元资产冻结库中的initWallet()无访问控制

3. INCOMPLETE CODE PATH

3. 代码路径不完整

#3 Critical — 17% of Criticals.
排名第3的Critical级漏洞 — 占Critical级漏洞的17%。

The Function Family Comparison Test

函数组对比测试法

1. List all state changes in function A (deposit/place/create)
2. List all state changes in function B (withdraw/update/cancel)
3. For each state change in A: does B have the corresponding reverse?
4. For each token transfer in A: does B have the corresponding refund?
If A does X but B doesn't do the reverse of X → BUG.
1. 列出函数A(deposit/place/create)中的所有状态变更
2. 列出函数B(withdraw/update/cancel)中的所有状态变更
3. 针对A中的每一项状态变更:B是否有对应的反向操作?
4. 针对A中的每一笔代币转账:B是否有对应的退款操作?
如果A执行了X但B未执行X的反向操作 → 存在漏洞。

Variant 1: Update Function Missing Refund (ThunderNFT)

变体1:更新函数缺失退款逻辑(ThunderNFT)

solidity
function place_order(OrderInput calldata order) external {
    token.safeTransferFrom(msg.sender, address(this), order.price);  // takes tokens
    orders[orderId] = order;
}
function update_order(OrderInput calldata updatedOrder) external {
    // BUG: NO REFUND for sell orders when price decreases → tokens permanently stuck
    orders[orderId] = updatedOrder;
}
solidity
function place_order(OrderInput calldata order) external {
    token.safeTransferFrom(msg.sender, address(this), order.price);  // 收取代币
    orders[orderId] = order;
}
function update_order(OrderInput calldata updatedOrder) external {
    // 漏洞:当售价降低时,卖出订单无退款逻辑 → 代币永久锁定
    orders[orderId] = updatedOrder;
}

Variant 2: Partial Fill Token Stuck (Plume)

变体2:部分填充导致代币锁定(Plume)

solidity
function swapForETH(uint256 amountIn) external {
    token.safeTransferFrom(msg.sender, address(this), amountIn);
    uint256 filled = dex.swap(amountIn);  // partial fill possible
    _refundExcessEth(amountIn - filled);  // BUG: refunds ETH only, not ERC20
}
solidity
function swapForETH(uint256 amountIn) external {
    token.safeTransferFrom(msg.sender, address(this), amountIn);
    uint256 filled = dex.swap(amountIn);  // 可能出现部分填充
    _refundExcessEth(amountIn - filled);  // 漏洞:仅退款ETH,未退款ERC20代币
}

Variant 3: mint() Bypasses Check That deposit() Has (MetaPool)

变体3:mint()绕过deposit()的校验逻辑(MetaPool)

solidity
function deposit(uint256 assets, address receiver) public override {
    shares = _deposit(assets, receiver);  // includes receipt validation
}
function mint(uint256 shares, address receiver) public override {
    assets = convertToAssets(shares);
    _mint(receiver, shares);  // MISSING: _deposit() validation → mints without receiving assets
}
solidity
function deposit(uint256 assets, address receiver) public override {
    shares = _deposit(assets, receiver);  // 包含收据验证
}
function mint(uint256 shares, address receiver) public override {
    assets = convertToAssets(shares);
    _mint(receiver, shares);  // 缺失:_deposit()验证 → 未接收资产就铸造份额
}

Grep Patterns

Grep检索模式

bash
grep -rn "function place_\|function create_\|function add_\|function open_" contracts/ -A5
grep -rn "function update_\|function modify_\|function cancel_" contracts/ -A5
grep -rn "safeApprove\b" contracts/    # safeApprove without zero-reset before
grep -rn "delete\b" contracts/ -B5 -A5  # delete before operation completes
grep -rn "function deposit\|function mint\|function withdraw\|function redeem" contracts/ -A10

bash
grep -rn "function place_\|function create_\|function add_\|function open_" contracts/ -A5
grep -rn "function update_\|function modify_\|function cancel_" contracts/ -A5
grep -rn "safeApprove\b" contracts/    // 未先重置为0的safeApprove操作
grep -rn "delete\b" contracts/ -B5 -A5  // 操作完成前执行delete
grep -rn "function deposit\|function mint\|function withdraw\|function redeem" contracts/ -A10

4. OFF-BY-ONE & BOUNDARY CONDITIONS

4. 边界溢出与边界条件

#4 High — 22% of Highs. Single character change. Massive impact.
排名第4的High级漏洞 — 占High级漏洞的22%。仅需修改单个字符,影响巨大。

Root Cause

根源

solidity
// VeChain Stargate — post-exit reward drain:
function _claimableDelegationPeriods(address delegator) internal view returns (uint256) {
    if (endPeriod > nextClaimablePeriod) {  // BUG: should be >=
        return 0;  // exited users get nothing
    }
    return nextClaimablePeriod - lastClaimedPeriod;  // rewards for period AFTER exit
}
solidity
// VeChain Stargate — 退出后奖励被盗:
function _claimableDelegationPeriods(address delegator) internal view returns (uint256) {
    if (endPeriod > nextClaimablePeriod) {  // 漏洞:应为>=
        return 0;  // 退出用户无法获得任何奖励
    }
    return nextClaimablePeriod - lastClaimedPeriod;  // 退出后仍可获取奖励
}

Mental Test for Every Comparison

所有比较逻辑的心智校验

For every
if (A > B)
: "What happens when A == B?" Is that correct?
对于每一处
if (A > B)
:"当A == B时会发生什么?"这是否符合预期?

6 Boundary Locations to Check

需检查的6类边界位置

  1. Period/Epoch boundaries:
    >
    vs
    >=
    at period end
  2. Time-based locks: does
    block.timestamp == deadline
    lock or unlock?
  3. Loop break conditions:
    break
    with
    >
    vs
    >=
  4. Array index boundaries:
    i <= array.length
    (should be
    i < array.length
    )
  5. Amount/balance boundaries:
    >= amount
    allows exact full withdrawal?
  6. Rounding/precision: can any input produce 0 output that should be non-zero?
  1. 周期/纪元边界:周期结束时使用
    >
    vs
    >=
  2. 时间锁:
    block.timestamp == deadline
    是锁定还是解锁?
  3. 循环终止条件:使用
    >
    vs
    >=
    触发
    break
  4. 数组索引边界:
    i <= array.length
    (应为
    i < array.length
  5. 金额/余额边界:
    >= amount
    是否允许全额提取?
  6. 舍入/精度:是否存在输入会导致本应非零的输出变为0?

Grep Patterns

Grep检索模式

bash
undefined
bash
undefined

Boundaries in comparisons

比较逻辑中的边界

grep -rn "Period|Epoch|Round|Deadline|period|epoch|deadline" contracts/ -A3 | grep "[<>][^=]"
grep -rn "Period|Epoch|Round|Deadline|period|epoch|deadline" contracts/ -A3 | grep "[<>][^=]"

Loop breaks

循环终止

grep -rn "\bbreak\b" contracts/ -B10
grep -rn "\bbreak\b" contracts/ -B10

Off-by-one in array access

数组访问中的边界溢出

grep -rn ".length\s*-\s1|i\s<=\s*.*.length\b" contracts/

---
grep -rn ".length\s*-\s1|i\s<=\s*.*.length\b" contracts/

---

5. ORACLE / PRICE MANIPULATION

5. 预言机/价格操纵

12% of all reports. Largest individual payouts. $117M Mango, $70M Curve.
占所有报告的12%。单笔赏金最高。Mango损失1.17亿美元,Curve损失7000万美元。

Bug A: Missing Staleness Check (most common)

漏洞A:缺失时效性检查(最常见)

solidity
// VULNERABLE:
(, int256 price,,,) = priceFeed.latestRoundData();
return uint256(price);  // If Chainlink node goes down, stale price returned indefinitely

// CORRECT:
(, int256 price,, uint256 updatedAt,) = priceFeed.latestRoundData();
require(block.timestamp - updatedAt <= MAX_PRICE_AGE, "Stale price");
require(price > 0, "Invalid price");
solidity
// 存在漏洞:
(, int256 price,,,) = priceFeed.latestRoundData();
return uint256(price);  // 若Chainlink节点宕机,将无限期返回过时价格

// 正确写法:
(, int256 price,, uint256 updatedAt,) = priceFeed.latestRoundData();
require(block.timestamp - updatedAt <= MAX_PRICE_AGE, "Stale price");
require(price > 0, "Invalid price");

Bug B: Missing Confidence Interval (Pyth)

漏洞B:缺失置信区间校验(Pyth)

solidity
// VULNERABLE:
PythStructs.Price memory p = pyth.getPriceUnsafe(priceFeed);
return p.price;  // ignores p.conf (confidence interval)

// CORRECT:
require(p.conf * 10 <= uint64(p.price), "Price too uncertain");
// conf > 10% of price = untrustworthy
solidity
// 存在漏洞:
PythStructs.Price memory p = pyth.getPriceUnsafe(priceFeed);
return p.price;  // 忽略p.conf(置信区间)

// 正确写法:
require(p.conf * 10 <= uint64(p.price), "Price too uncertain");
// conf > 价格的10% = 不可信

Bug C: TWAP Too Short (flash loan manipulatable)

漏洞C:TWAP窗口过短(可被闪电贷操纵)

solidity
// VULNERABLE: 60-second TWAP
uint32[] memory secondsAgos = new uint32[](2);
secondsAgos[0] = 60; secondsAgos[1] = 0;
// Flash loan can shift price for entire 60s window

// CORRECT: 1800s minimum TWAP (30 min)
solidity
// 存在漏洞:60秒TWAP
uint32[] memory secondsAgos = new uint32[](2);
secondsAgos[0] = 60; secondsAgos[1] = 0;
// 闪电贷可在整个60秒窗口内操纵价格

// 正确写法:最小1800秒TWAP(30分钟)

Bug D: Single-Source Oracle

漏洞D:单一数据源预言机

solidity
// VULNERABLE: only Uniswap spot price
uint price = getUniswapSpotPrice(token);  // flash loan manipulatable

// CORRECT: Chainlink primary, Uniswap TWAP as fallback, require close agreement
solidity
// 存在漏洞:仅使用Uniswap现货价格
uint price = getUniswapSpotPrice(token);  // 可被闪电贷操纵

// 正确写法:以Chainlink为主要数据源,Uniswap TWAP为备用,需确保两者价格接近

Grep Patterns

Grep检索模式

bash
undefined
bash
undefined

Missing staleness check

缺失时效性检查

grep -rn "latestRoundData" contracts/ -A5 | grep -v "updatedAt|timestamp"
grep -rn "latestRoundData" contracts/ -A5 | grep -v "updatedAt|timestamp"

Pyth price usage — confidence interval checked?

Pyth价格使用 — 是否校验置信区间?

grep -rn "getPriceUnsafe|getPrice\b" contracts/ -A8 | grep -v "conf|confidence"
grep -rn "getPriceUnsafe|getPrice\b" contracts/ -A8 | grep -v "conf|confidence"

TWAP windows — short TWAP flag

TWAP窗口 — 短TWAP标记

grep -rn "secondsAgo|TWAP|cardinality" contracts/ -A5

---
grep -rn "secondsAgo|TWAP|cardinality" contracts/ -A5

---

6. ERC4626 VAULT ATTACKS

6. ERC4626 金库攻击

Exchange Rate Manipulation (near-empty vault)

汇率操纵(近乎空金库)

solidity
// VULNERABLE — first depositor attack:
// 1. Attacker deposits 1 wei → gets 1 share
// 2. Attacker donates large amount directly (transfer, not deposit)
// 3. Exchange rate: 1 share = (1 + donation) assets
// 4. Victim deposits → rounds down to 0 shares → free donation to attacker

// CORRECT: virtual shares (OpenZeppelin v4.9+)
function _decimalsOffset() internal view virtual override returns (uint8) {
    return 9;  // add 1e9 virtual shares + assets to prevent manipulation
}
solidity
// 存在漏洞 — 首次存款攻击:
// 1. 攻击者存入1 wei → 获取1份额
// 2. 攻击者直接捐赠大量资产(transfer而非deposit)
// 3. 汇率:1份额 = (1 + 捐赠额)资产
// 4. 受害者存款 → 份额向下取整为0 → 资产无偿捐赠给攻击者

// 正确写法:虚拟份额(OpenZeppelin v4.9+)
function _decimalsOffset() internal view virtual override returns (uint8) {
    return 9;  // 添加1e9虚拟份额+资产以防止操纵
}

ERC4626 Transfer (moves shares but not stake/lock records)

ERC4626转账(仅转移份额但未转移质押/锁定记录)

solidity
// VULNERABLE: shares transferred, but lock records stay with original owner
// → shares stuck, can't redeem → permanent freeze (Belong pattern)
function transfer(address to, uint256 amount) external override {
    _transfer(msg.sender, to, amount);  // moves shares
    // MISSING: transfer lock record from msg.sender to `to`
}
solidity
// 存在漏洞:份额已转移,但锁定记录仍属于原所有者
// → 份额被锁定,无法赎回 → 永久冻结(Belong模式)
function transfer(address to, uint256 amount) external override {
    _transfer(msg.sender, to, amount);  // 转移份额
    // 缺失:将锁定记录从msg.sender转移至`to`
}

Grep Patterns

Grep检索模式

bash
grep -rn "function transfer\|function transferFrom" contracts/ -A15
grep -rn "function deposit\|function mint\|function withdraw\|function redeem" contracts/ -A10

bash
grep -rn "function transfer\|function transferFrom" contracts/ -A15
grep -rn "function deposit\|function mint\|function withdraw\|function redeem" contracts/ -A10

7. REENTRANCY

7. 重入攻击

2016–present. CEI pattern prevents it. Still found in DeFi.
2016年至今。CEI模式可预防,但仍在DeFi中被发现。

Variants

变体

  • Single-function: attacker re-enters same function before state updated
  • Cross-function: re-enters a sibling function with stale state
  • Cross-contract: re-enters via a callback to another protocol
  • Read-only: re-enters a view function that returns stale data used by attacker
  • 单函数重入:攻击者在状态更新前重新进入同一函数
  • 跨函数重入:重新进入关联函数,利用过时状态
  • 跨合约重入:通过回调重新进入另一协议
  • 只读重入:重新进入视图函数,获取攻击者可利用的过时数据

Root Cause Pattern

根源模式

solidity
// VULNERABLE (effects after interaction):
function withdraw(uint256 amount) external {
    require(balances[msg.sender] >= amount);
    (bool success,) = msg.sender.call{value: amount}("");  // INTERACTION first
    require(success);
    balances[msg.sender] -= amount;  // EFFECT after → reentrancy window
}

// CORRECT (CEI — Checks, Effects, Interactions):
function withdraw(uint256 amount) external {
    require(balances[msg.sender] >= amount);  // CHECK
    balances[msg.sender] -= amount;            // EFFECT
    (bool success,) = msg.sender.call{value: amount}("");  // INTERACTION last
    require(success);
}
solidity
// 存在漏洞(交互后更新状态):
function withdraw(uint256 amount) external {
    require(balances[msg.sender] >= amount);
    (bool success,) = msg.sender.call{value: amount}("");  // 先执行交互
    require(success);
    balances[msg.sender] -= amount;  // 后更新状态 → 存在重入窗口
}

// 正确写法(CEI — 校验、状态更新、交互):
function withdraw(uint256 amount) external {
    require(balances[msg.sender] >= amount);  // 校验
    balances[msg.sender] -= amount;            // 状态更新
    (bool success,) = msg.sender.call{value: amount}("");  // 最后执行交互
    require(success);
}

Grep Patterns

Grep检索模式

bash
undefined
bash
// 状态更新前的外部调用
grep -rn "\.call{value\|safeTransfer\|transfer(" contracts/ -B10 | grep -v "require\|revert"

// 关键函数缺失nonReentrant修饰器
grep -rn "function withdraw\|function redeem\|function claim" contracts/ -A2 | grep -v "nonReentrant"

// 重入防护的存储槽
grep -rn "nonReentrant\|ReentrancyGuard\|_notEntered" contracts/

External calls before state updates

8. 闪电贷攻击

通过闪电贷操纵预言机

grep -rn ".call{value|safeTransfer|transfer(" contracts/ -B10 | grep -v "require|revert"
solidity
// 攻击流程:
// 1. 从Aave借入1亿美元闪电贷
// 2. 在Uniswap池抛售代币 → 压低现货价格
// 3. 协议读取Uniswap现货价格 → 接受抵押不足的贷款
// 4. 以低价抵押品借入最大额度
// 5. 偿还闪电贷,保留利润

Missing nonReentrant modifier on critical functions

价格预言机合理性检查(需关注内容)

grep -rn "function withdraw|function redeem|function claim" contracts/ -A2 | grep -v "nonReentrant"
bash
grep -rn "getReserves\|getAmountsOut\|slot0\b" contracts/ -A5
// 从储备获取现货价格 = 可被闪电贷操纵
// slot0 = Uniswap V3现货价格 = 可被操纵

Storage slot for reentrancy guard

9. 签名重放

缺失随机数(Nonce)

grep -rn "nonReentrant|ReentrancyGuard|_notEntered" contracts/

---
solidity
// 存在漏洞:
function permit(address owner, address spender, uint256 value,
                uint256 deadline, uint8 v, bytes32 r, bytes32 s) external {
    bytes32 hash = keccak256(abi.encodePacked(owner, spender, value, deadline));
    // 缺失:未包含nonce → 同一签名可重复使用
    require(ecrecover(hash, v, r, s) == owner);
}

8. FLASH LOAN ATTACKS

缺失链ID

Oracle Manipulation via Flash Loan

solidity
// Attack flow:
// 1. Borrow $100M from Aave flash loan
// 2. Dump token in Uniswap pool → crash spot price
// 3. Protocol reads Uniswap spot → undercollateralized loans accepted
// 4. Borrow max against cheap collateral
// 5. Repay flash loan, keep profits
solidity
// 存在漏洞:签名在主网、测试网及所有分叉链均有效
bytes32 hash = keccak256(abi.encodePacked(params));
// 缺失:hash中未包含block.chainid → 在任意链上均可生效

Price Oracle Sanity Checks (what to look for)

Grep检索模式

bash
grep -rn "getReserves\|getAmountsOut\|slot0\b" contracts/ -A5
bash
grep -rn "ecrecover\|ECDSA\.recover" contracts/ -B20
// 检查:签名哈希是否包含nonce + chainId + 合约地址?

grep -rn "nonce\|_nonces\|nonces\[" contracts/

spot price from reserves = manipulatable with flash loan

10. 代理/升级问题

slot0 = Uniswap V3 spot price = manipulatable

存储冲突


---
solidity
// 实现合约与代理合约共享存储布局
// 代理存储槽0:_owner
// 实现合约存储槽0:_initialized
// → 写入_initialized会覆盖_owner

9. SIGNATURE REPLAY

未初始化的实现合约

Missing Nonce

solidity
// VULNERABLE:
function permit(address owner, address spender, uint256 value,
                uint256 deadline, uint8 v, bytes32 r, bytes32 s) external {
    bytes32 hash = keccak256(abi.encodePacked(owner, spender, value, deadline));
    // MISSING: nonce not included → same signature usable multiple times
    require(ecrecover(hash, v, r, s) == owner);
}
solidity
// 若实现合约可直接初始化 → 任何人都可成为实现合约的所有者
// 攻击方式:调用实现合约的initialize() → 调用upgradeTo() → 替换逻辑

Missing Chain ID

委托调用至用户可控地址

solidity
// VULNERABLE: signature valid on mainnet AND testnet AND all forks
bytes32 hash = keccak256(abi.encodePacked(params));
// MISSING: block.chainid not in hash → works on any chain
solidity
function execute(address target, bytes calldata data) external onlyOwner {
    target.delegatecall(data);  // target已验证,但如果所有者被攻陷呢?
}

Grep Patterns

Grep检索模式

bash
grep -rn "ecrecover\|ECDSA\.recover" contracts/ -B20
bash
// UUPS初始化防护
grep -rn "function initialize\b\|_disableInitializers\|initializer" contracts/

// 委托调用
grep -rn "delegatecall\b" contracts/ -B3 -A5

// 存储布局 — 代理是否使用EIP-1967存储槽?
grep -rn "0x360894\|EIP1967\|_IMPLEMENTATION_SLOT" contracts/

Check: does the signed hash include nonce + chainId + contract address?

FOUNDRY POC模板

grep -rn "nonce|_nonces|nonces[" contracts/

---
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "forge-std/Test.sol";
import "../src/VulnerableContract.sol";

contract ExploitTest is Test {
    VulnerableContract target;
    address attacker = makeAddr("attacker");
    address victim = makeAddr("victim");

    function setUp() public {
        // 分叉主网至指定区块
        vm.createSelectFork("mainnet", BLOCK_NUMBER);

        // 部署或加载目标合约
        target = VulnerableContract(TARGET_ADDRESS);

        // 为账户充值
        deal(address(token), attacker, INITIAL_BALANCE);
        deal(address(token), victim, VICTIM_BALANCE);
    }

    function test_exploit() public {
        console.log("Attacker balance before:", token.balanceOf(attacker));

        vm.startPrank(attacker);

        // 步骤1:设置攻击条件
        // 步骤2:执行攻击
        // 步骤3:验证攻击效果

        vm.stopPrank();

        console.log("Attacker balance after:", token.balanceOf(attacker));
        assertGt(token.balanceOf(attacker), INITIAL_BALANCE, "Exploit failed");
    }
}

10. PROXY / UPGRADE ISSUES

Foundry核心作弊码

Storage Collision

solidity
// Implementation and proxy share storage layout
// Proxy slot 0: _owner
// Implementation slot 0: _initialized
// → writing to _initialized overwrites _owner
solidity
vm.prank(address)           // 下一次调用来自指定地址
vm.startPrank(address)      // 所有调用来自指定地址,直到stopPrank()
vm.deal(address, amount)    // 设置ETH余额
deal(token, address, amount) // 设置ERC20余额
vm.warp(timestamp)          // 设置block.timestamp
vm.roll(blockNumber)        // 设置block.number
vm.createSelectFork("mainnet", blockNumber)  // 分叉主网
vm.expectRevert(bytes)      // 下一次调用应触发回滚
vm.label(address, "name")   // 为追踪输出添加标签
vm.assume(condition)        // 模糊测试:丢弃条件为false的输入

Uninitialized Implementation

运行测试

solidity
// If implementation can be initialized directly → anyone becomes owner of implementation
// Attack: call initialize() on implementation contract → call upgradeTo() → replace logic
bash
undefined

delegatecall to User-Controlled Address

运行指定测试

solidity
function execute(address target, bytes calldata data) external onlyOwner {
    target.delegatecall(data);  // target is validated, but what if owner is compromised?
}
forge test --match-test test_exploit -vvvv

Grep Patterns

带分叉运行测试

bash
undefined
forge test --match-test test_exploit -vvvv --fork-url $MAINNET_RPC

UUPS initialization protection

生成gas报告

grep -rn "function initialize\b|_disableInitializers|initializer" contracts/
forge test --gas-report

Delegate call

生成覆盖率报告

grep -rn "delegatecall\b" contracts/ -B3 -A5
forge coverage --report summary

---

Storage layout — proxy uses EIP-1967 slots?

相关技能与链

grep -rn "0x360894|EIP1967|_IMPLEMENTATION_SLOT" contracts/

---
  • meme-coin-audit
    — 当目标是meme币/SPL代币而非DeFi协议时。流程差异:审计前筛选排除信号不同 — 本技能的"TVL<50万美元则跳过"不适用于meme币,这类代币的审计核心是 rug检查(铸币权限、冻结权限、LP锁定);此类场景应使用
    meme-coin-audit
    技能。
  • triage-validation
    — 当合约漏洞发现准备提交至Immunefi时。流程要点:Immunefi有自己的报告格式,但
    triage-validation
    中对影响验证、链端到端测试的要求仍适用;提交前需针对Foundry PoC运行7Q校验。
  • report-writing
    — 撰写Immunefi报告正文时。流程要点:
    report-writing
    的Immunefi模板(包含Foundry PoC、根源代码片段、量化经济影响)是本技能漏洞发现内容的载体框架。
  • offensive-osint
    — 审计协议的链下攻击面(前端、管理API、RPC网关)时。流程要点:链上审计是本技能的职责;协议的任何Web2组件(前端、管理面板、索引器API)需交由
    offensive-osint
    技能进行侦察。
  • bb-methodology
    — 决定是否开始审计时。流程要点:
    bb-methodology
    的第0部分确认参与类型(Web3漏洞赏金/私有审计/智能合约审查);本技能的审计前筛选排除信号替代该参与类型的标准评分规则。

FOUNDRY POC TEMPLATE

操作说明(Claude-BugHunter)

solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "forge-std/Test.sol";
import "../src/VulnerableContract.sol";

contract ExploitTest is Test {
    VulnerableContract target;
    address attacker = makeAddr("attacker");
    address victim = makeAddr("victim");

    function setUp() public {
        // Fork mainnet at specific block
        vm.createSelectFork("mainnet", BLOCK_NUMBER);

        // Deploy or load target
        target = VulnerableContract(TARGET_ADDRESS);

        // Fund accounts
        deal(address(token), attacker, INITIAL_BALANCE);
        deal(address(token), victim, VICTIM_BALANCE);
    }

    function test_exploit() public {
        console.log("Attacker balance before:", token.balanceOf(attacker));

        vm.startPrank(attacker);

        // Step 1: Setup conditions
        // Step 2: Execute exploit
        // Step 3: Verify impact

        vm.stopPrank();

        console.log("Attacker balance after:", token.balanceOf(attacker));
        assertGt(token.balanceOf(attacker), INITIAL_BALANCE, "Exploit failed");
    }
}
基于实际项目经验 + 2026年专属补充内容。 来自真实授权项目的经验 + 本仓库31+技能领域测试的第二阶段验证。上游内容覆盖"是什么";本层内容覆盖"何时有效vs何时无效"。

Key Foundry Cheatcodes

2026年仍能获得赏金的漏洞类别

solidity
vm.prank(address)           // next call from address
vm.startPrank(address)      // all calls from address until stopPrank()
vm.deal(address, amount)    // set ETH balance
deal(token, address, amount) // set ERC20 balance
vm.warp(timestamp)          // set block.timestamp
vm.roll(blockNumber)        // set block.number
vm.createSelectFork("mainnet", blockNumber)  // fork mainnet
vm.expectRevert(bytes)      // next call should revert
vm.label(address, "name")   // label for trace output
vm.assume(condition)        // fuzz: discard inputs where false
闪电贷攻击仍是Immunefi平台赏金最高的类别之一(2024-2026年排名前5)。其经济原理——借入5000万美元、操纵价格预言机、掏空资金池、偿还贷款——持续出现,因为新协议不断上线,而它们的可组合性假设在闪电贷导致的失衡场景下并不成立。
重入攻击仍能获得赏金,因为新协议不断使用ERC-777/钩子/回调。不要认为这类漏洞已消失——2023-2025年的赏金案例库包含40+针对CEI模式代码的重入漏洞(跨函数重入、状态变更过程中调用视图函数导致的只读重入)。
预言机操纵:赏金仍高,但难度更大。大多数项目现在使用Chainlink价格数据源;攻击目标是大多数项目同时使用的次要预言机(低流动性Uniswap V2池的TWAP、协议内部预言机、过时的 fallback路径)。审计故障转移链,而非仅关注主数据源。

Running Tests

自上游内容发布后的新增内容

bash
undefined
  • EIP-1153(临时存储) — 2024年引入。新型重入类别:同一交易中临时存储的读取会与持久存储不同步。审计任何
    tload
    /
    tstore
    使用场景,检查外部调用后的读取操作。
  • EIP-7702(2025年Pectra硬分叉) — 新增EOA到智能账户的升级功能。通过重新授权实现类似ATO的新攻击原语:EOA一次签名即可授权给攻击者控制的合约,然后在授权间进行签名重放。
  • 账户抽象(ERC-4337打包器) — 支付方赞助滥用和打包器恶意攻击。未强制严格发送者白名单的支付方合约会在首次调用时被掏空。
  • ZK-rollup桥漏洞 — 跨rollup的证明重放、链下 prover攻陷、 sequencer审查导致的强制包含边缘案例。
  • LST/LRT脱钩动态 — 流动质押和流动再质押代币假设在亏损场景下保持1:1挂钩;预言机假设挂钩,但市场反映脱钩,清算逻辑失效。

Run specific test

2026年工具栈

forge test --match-test test_exploit -vvvv
Foundry仍是测试框架。使用
forge test --gas-report --debug
进行不变量测试;
forge fuzz
进行基于属性的测试;
forge inspect
进行存储布局审计。Slither + Echidna用于静态分析+模糊测试。Mythril用于小型合约的符号执行。tenderly.co用于分叉+模拟(复现主网状态下攻击的最佳UX)。
对于Solana:使用anchor框架、sealevel-attacks案例库(anchor维护者整理的PoC)、soteria-sec / sec3扫描器。对于Move(Aptos、Sui):使用move-prover、aptos-cli的
aptos move test
对于跨链:Hyperlane和LayerZero均有审计工具仓库;桥漏洞需要模拟两端点,而非仅一端。

Run with fork

审计前筛选排除信号的适用场景

forge test --match-test test_exploit -vvvv --fork-url $MAINNET_RPC
TVL低于50万美元的项目不值得投入审计时间,除非赏金下限很高。审计公司已覆盖的项目=低ROI,除非你能发现他们遗漏的内容——查看审计报告的范围排除部分,了解他们明确未审计的内容(预言机、治理、链下组件、前端、管理路径)。
多签签名者>5 + 时间锁>48小时=低rug风险;如果你的漏洞发现需要假设团队恶意,那么其影响程度低,且可能因Immunefi的"中心化风险"排除条款而超出范围。阅读项目简介——大多数Immunefi项目明确降级或拒绝基于管理员恶意假设的漏洞发现。

Gas report

报告规范

forge test --gas-report
Immunefi要求提交Foundry PoC。无PoC的提交会被自动拒绝。需手动设置的PoC("先部署这个,再调用那个")通常会被降级——PoC应是单次
forge test
调用即可证明影响,包含明确的
assertEq
验证被盗余额/铸造代币/损坏状态。
严重程度声明必须严格使用Immunefi的严重程度矩阵;不要自定义严重程度。该矩阵基于TVL的直接经济损失百分比——针对50万美元协议的Critical级漏洞与针对5亿美元协议的Critical级漏洞赏金不同。提交前阅读项目的具体严重程度分配规则。

Coverage

forge coverage --report summary

---

Related Skills & Chains

  • meme-coin-audit
    — When the target is a meme coin / SPL token rather than a DeFi protocol. Workflow primitive: pre-dive kill signals diverge — this skill's "TVL < $500K skip" doesn't apply to meme coins where the rug check (mint authority, freeze authority, LP lock) is the entire audit; route to
    meme-coin-audit
    instead.
  • triage-validation
    — When a contract finding is ready to be filed on Immunefi. Workflow primitive: Immunefi has its own report format, but the impact-validated, chain-end-to-end discipline of
    triage-validation
    still applies; run the 7Q gate against the Foundry PoC before submitting.
  • report-writing
    — When writing the Immunefi report body. Workflow primitive:
    report-writing
    's Immunefi template (with Foundry PoC, root cause code snippet, quantified economic impact) is the body skeleton this skill's findings feed into.
  • offensive-osint
    — When auditing a protocol's off-chain attack surface (frontend, admin API, RPC gateways). Workflow primitive: on-chain audit is this skill's job; any web2 component of the protocol (web-frontend, admin panel, indexer API) routes to
    offensive-osint
    for recon.
  • bb-methodology
    — When deciding whether to dive at all. Workflow primitive: PART 0 of
    bb-methodology
    confirms engagement (web3 bug bounty / private audit / smart-contract review); this skill's pre-dive kill signals replace the standard scoring rubric for that engagement type.

Operator Notes (Claude-BugHunter)

Engagement-derived + 2026-specific additions to the vendored foundation. Wisdom from real authorized engagements + Phase 2 verification across this repo's 31+ skill-area live tests. The upstream content covers the WHAT; this layer covers the WHEN-IT-WORKS-vs-WHEN-IT-DOESN'T.

Bug classes still paying in 2026

Flash-loan attacks remain top-paid on Immunefi (top 5 in 2024-2026 by bounty). The economic primitive — borrow $50M, manipulate price oracle, drain pool, repay — keeps reappearing because new protocols keep shipping with composability assumptions that don't hold under flash-loaned imbalance.
Reentrancy IS still paying because new protocols keep shipping with ERC-777 / hooks / callbacks. Don't assume the class is dead — the 2023-2025 paid corpus contains 40+ reentrancy bugs against post-Checks-Effects-Interactions codebases (cross-function reentrancy, read-only reentrancy via view functions called during state-mid-flight).
Oracle manipulation: still paid heavily but harder. Most projects use Chainlink price feeds now; the attack target is the SECONDARY oracle most projects also use (TWAP from a low-liquidity Uniswap V2 pair, the protocol's own internal oracle, a stale fallback path). Audit the failover chain, not just the primary feed.

What's new since the vendored content was written

  • EIP-1153 (transient storage) — introduced in 2024. New reentrancy classes: transient-storage reads cached across the same transaction can desync from persistent storage. Audit any
    tload
    /
    tstore
    usage for read-after-external-call.
  • EIP-7702 (Pectra hard fork 2025) — added EOA-to-smart-account upgrades. New ATO-like primitives via re-delegation: an EOA signed-once can delegate to a contract that the attacker controls, then signature replay across delegations.
  • Account abstraction (ERC-4337 bundlers) — paymaster sponsorship abuse and bundler griefing. Paymaster contracts that don't enforce strict sender allowlists drain on first call.
  • ZK-rollup bridge bugs — proof-replay across rollups, off-chain prover compromise, sequencer censorship leading to forced-inclusion edge cases.
  • LST/LRT depeg dynamics — liquid-staking and liquid-restaking tokens that assume 1:1 peg under loss conditions; oracle assumes peg, market reflects depeg, liquidation logic breaks.

Tool stack for 2026

Foundry remains the test framework.
forge test --gas-report --debug
for invariant testing;
forge fuzz
for property-based testing;
forge inspect
for storage-layout audits. Slither + Echidna for static + fuzz. Mythril for symbolic execution on smaller contracts. tenderly.co for forking + simulation (best UX for replicating attacks against mainnet state).
For Solana: anchor framework, sealevel-attacks corpus (curated PoCs by anchor maintainers), soteria-sec / sec3 scanner. For Move (Aptos, Sui): move-prover, aptos-cli
aptos move test
.
For cross-chain: hyperlane and LayerZero each have audit-tooling repos; bridge bugs require simulating both endpoints, not just one.

Where pre-dive kill signals matter

TVL under $500K isn't worth the audit time unless the bounty floor is high. Audit firm already covered it = low ROI unless you find what they missed — look at the audit-report scope-exclusion section for what they EXPLICITLY didn't audit (oracles, governance, off-chain components, frontend, the admin path).
Multisig signers > 5 + timelock > 48h = low rug-pull risk; if your finding requires team-malicious assumptions, it's low-impact and likely out of scope per Immunefi's "centralization risk" exclusion. Read the program brief — most Immunefi programs explicitly downgrade or reject findings that assume admin malice.

Reporting discipline

Immunefi requires Foundry PoC. Submission without PoC is auto-rejected. Submission with a PoC that requires manual setup ("first deploy this, then call that") usually gets downgraded — the PoC should be a single
forge test
invocation that proves the impact, with explicit
assertEq
on the drained balance / minted token / corrupted state.
Severity claims must use Immunefi's severity matrix exactly; don't invent severities. The matrix gates on direct economic loss percentage of TVL — a critical against a $500K protocol pays differently than a critical against a $500M one. Read the program's specific severity assignment before claiming Critical.