关于 web3-audit
web3-audit 是面向安全研究人员的智能合约审计技能,提供目标筛选方法、漏洞模式、代码搜索建议和审计示例,用于检查 Web3 协议。
主要功能
- 01审计前目标筛选
- 02十类智能合约漏洞
- 03Solidity 搜索模式
- 04对照同组函数
- 05Foundry PoC 模板
- 06真实付费漏洞案例
适用场景
- 01审计前筛选赏金目标
- 02检查记账与访问控制逻辑
- 03比较相关合约代码路径
- 04编写概念验证测试
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ uphiago/ recon-skills --skill web3-audit安装后在 Claude Code 中输入/web3-audit确认已加载。
详细介绍
Web3 智能合约审计
10 类漏洞。审计前的止损信号。Foundry PoC 模板。真实付费案例。
审计前止损信号(代码审查前务必检查)
ZKsync 的教训:3.22 亿美元 TVL + OZ 审计 + 75 万行代码 + 5 次审计 = 0 个发现。大型且经过充分审计的跨链桥极难发现问题。
- TVL < $500K → 最高奖金太低,不值得投入精力
- 有 2 次或更多顶级审计(Halborn、ToB、Cyfrin、OpenZeppelin),且协议简单 → 漏洞已经被发现
- 协议代码 < 500 行,只有单一的 A→B→C 流程 → 攻击面极小
- 公式:
max_realistic_payout = min(10% × TVL, program_cap)— 如果低于 $10K,就跳过
软性止损: 当前版本经过 OZ/ToB/Cyfrin 审计,且代码库 > 50 万行 → 预计要花 40 小时以上,可能只找到 1 个问题。只有在奖金下限 > $50K 且你具备该协议的专业知识时才继续。
目标评分(>= 6/10 才继续):
- TVL > $10M:+2
- Immunefi 计划的 Critical 奖金 >= $50K:+2
- 当前版本未经顶级审计:+2
- 距离部署时间 < 30 天:+1
- 你以前审计过该协议:+1
- 有源代码 + natspec 注释:+1
- 可升级代理:+1
唯一规则
“阅读所有同类函数。如果
vote()有修饰符,就检查poke()、reset()、harvest()。同类函数缺少修饰符本身就是漏洞。”
这一条规则可以解释 Immunefi 上 19% 的 Critical 发现。
1. 记账状态不同步
排名第 1 的 Critical 漏洞类别——占 Immunefi 上所有 Critical 的 28%。
漏洞是什么
两个状态变量本应保持同步。某条代码路径更新了 A,却忘记更新 B。后续代码读取这两个变量,并依据过期的 B 作出判断。
Real Value = A - B
If A updated but B isn't → Real Value appears larger → phantom value
根因模式
变体 1:虚假收益(Yeet 协议——35 份重复报告)
function startUnstake(uint256 amount) external {
totalSupply -= amount; // decremented BEFORE transfer
// aToken.balanceOf(this) still reflects old value
// yieldAmount = aToken.balanceOf - totalSupply = phantom yield
}
变体 2:快速路径跳过状态更新(Alchemix V3)
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(...);
}
变体 3:更新顺序错误(Alchemix)
function deposit(uint256 amount) external {
_shares = (amount * totalShares) / totalAssets; // calculated BEFORE deposit
totalAssets += amount; // assets added AFTER shares calculated → wrong rate
}
Grep 模式
# Find all accounting variables
grep -rn "totalSupply\|totalShares\|totalAssets\|totalDebt\|cumulativeReward\|rewardPerShare" contracts/
# Find all early returns in claim/redeem functions
grep -rn "\breturn\b" contracts/ -B3 | grep -B3 "if\b"
# For each early return: which state updates in normal path are skipped?
2. 访问控制
排名第 2 的 Critical——占 Critical 的 19%。仅 2024 年就损失了 9.53 亿美元。
变体 1:同类函数缺少修饰符
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
}
变体 2:检查错误(是否存在,而非是否拥有)
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
}
变体 3:无提示修饰符(if 而非 require)
// 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"); _;
变体 4:代理未初始化
function initialize(address _owner) public { // MISSING: initializer modifier
owner = _owner; // anyone can call → become owner
}
// Fix: constructor() { _disableInitializers(); }
Grep 模式
# 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
# Ownership check: existence vs ownership?
grep -rn "_requireOwned\|ownerOf\|_isApprovedOrOwner\|_checkAuthorized" contracts/ -B5
# Silent modifiers
grep -rn "modifier\b" contracts/ -A8 | grep -B3 "if (" | grep -v "require\|revert"
# Uninitialized initializer
grep -rn "function initialize\b" contracts/ -A3
grep -rn "_disableInitializers()" contracts/
真实付费案例
| 协议 | 奖金 | 漏洞 |
|---|---|---|
| Wormhole | $10M | UUPS 代理未初始化 → 任何人都能调用 initialize() |
| ZeroLend | 不适用 | split() 检查是否存在,而非是否拥有 |
| Alchemix | 不适用 | poke() 缺少 onlyNewEpoch → FLUX 无限增发 |
| Parity | 冻结 $150M | 库中的 initWallet() 没有访问控制 |
3. 代码路径不完整
排名第 3 的 Critical——占 Critical 的 17%。
函数族对比测试
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:更新函数遗漏退款(ThunderNFT)
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;
}
变体 2:部分成交的代币被卡住(Plume)
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
}
变体 3:mint() 绕过了 deposit() 中的检查(MetaPool)
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
}
Grep 模式
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
4. 差一错误与边界条件
排名第 4 的 High——占 High 的 22%。只改一个字符,影响巨大。
根因
// 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
}
每个比较条件都要进行的思考测试
对每个
if (A > B),都问自己:“当 A == B 时会发生什么?”结果是否正确?
需要检查的 6 个边界位置
- 周期/纪元边界:周期结束时,
>与>=的区别 - 基于时间的锁:当
block.timestamp == deadline时,是锁定还是解锁? - 循环终止条件:使用
>还是>=的break - 数组索引边界:
i <= array.length(应为i < array.length) - 数量/余额边界:
>= amount是否允许精确全额提款? - 舍入/精度:是否存在某种输入会导致应为非零的输出变为 0?
Grep 模式
# Boundaries in comparisons
grep -rn "Period\|Epoch\|Round\|Deadline\|period\|epoch\|deadline" contracts/ -A3 | grep "[<>][^=]"
# Loop breaks
grep -rn "\bbreak\b" contracts/ -B10
# Off-by-one in array access
grep -rn "\.length\s*-\s*1\|i\s*<=\s*.*\.length\b" contracts/
5. 预言机/价格操纵
占所有报告的 12%。单笔最高奖金。Mango 损失 1.17 亿美元,Curve 损失 7000 万美元。
漏洞 A:缺少数据过期检查(最常见)
// 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");
漏洞 B:缺少置信区间检查(Pyth)
// 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
漏洞 C:TWAP 时间窗口过短(可被闪电贷操纵)
// 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)
漏洞 D:单一来源预言机
// VULNERABLE: only Uniswap spot price
uint price = getUniswapSpotPrice(token); // flash loan manipulatable
// CORRECT: Chainlink primary, Uniswap TWAP as fallback, require close agreement
Grep 模式
# Missing staleness check
grep -rn "latestRoundData" contracts/ -A5 | grep -v "updatedAt\|timestamp"
# Pyth price usage — confidence interval checked?
grep -rn "getPriceUnsafe\|getPrice\b" contracts/ -A8 | grep -v "conf\|confidence"
# TWAP windows — short TWAP flag
grep -rn "secondsAgo\|TWAP\|cardinality" contracts/ -A5
6. ERC4626 金库攻击
汇率操纵(近乎空置的金库)
// 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
}
ERC4626 转账(转移份额,但不转移质押/锁定记录)
// 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`
}
Grep 模式
grep -rn "function transfer\|function transferFrom" contracts/ -A15
grep -rn "function deposit\|function mint\|function withdraw\|function redeem" contracts/ -A10
7. 重入攻击
2016 年至今。CEI 模式可以防止此类攻击,但 DeFi 中仍时有发现。
变体
- 单函数:攻击者在状态更新前重新进入同一函数
- 跨函数:攻击者使用过期状态重新进入同级函数
- 跨合约:通过回调重新进入另一个协议
- 只读:重新进入返回过期数据的 view 函数,供攻击者使用
根本原因模式
// 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);
}
Grep 模式
# External calls before state updates
grep -rn "\.call{value\|safeTransfer\|transfer(" contracts/ -B10 | grep -v "require\|revert"
# Missing nonReentrant modifier on critical functions
grep -rn "function withdraw\|function redeem\|function claim" contracts/ -A2 | grep -v "nonReentrant"
# Storage slot for reentrancy guard
grep -rn "nonReentrant\|ReentrancyGuard\|_notEntered" contracts/
8. 闪电贷攻击
通过闪电贷操纵预言机
// 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
价格预言机合理性检查(检查要点)
grep -rn "getReserves\|getAmountsOut\|slot0\b" contracts/ -A5
# spot price from reserves = manipulatable with flash loan
# slot0 = Uniswap V3 spot price = manipulatable
9. 签名重放
缺少 Nonce
// 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);
}
缺少 Chain ID
// 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
Grep 模式
grep -rn "ecrecover\|ECDSA\.recover" contracts/ -B20
# Check: does the signed hash include nonce + chainId + contract address?
grep -rn "nonce\|_nonces\|nonces\[" contracts/
10. 代理/升级问题
存储冲突
// Implementation and proxy share storage layout
// Proxy slot 0: _owner
// Implementation slot 0: _initialized
// → writing to _initialized overwrites _owner
未初始化的实现合约
// If implementation can be initialized directly → anyone becomes owner of implementation
// Attack: call initialize() on implementation contract → call upgradeTo() → replace logic
对用户控制的地址执行 delegatecall
function execute(address target, bytes calldata data) external onlyOwner {
target.delegatecall(data); // target is validated, but what if owner is compromised?
}
Grep 模式
# UUPS initialization protection
grep -rn "function initialize\b\|_disableInitializers\|initializer" contracts/
# Delegate call
grep -rn "delegatecall\b" contracts/ -B3 -A5
# Storage layout — proxy uses EIP-1967 slots?
grep -rn "0x360894\|EIP1967\|_IMPLEMENTATION_SLOT" contracts/
FOUNDRY POC 模板
// 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");
}
}
Foundry 关键作弊码
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
运行测试
# Run specific test
forge test --match-test test_exploit -vvvv
# Run with fork
forge test --match-test test_exploit -vvvv --fork-url $MAINNET_RPC
# Gas report
forge test --gas-report
# Coverage
forge coverage --report summary
验证
- Solana 工具——确认 Anchor/SPL 工具可用:
which anchor 2>/dev/null && echo "PASS: anchor CLI found" || echo "NOTE: anchor not installed" which solana 2>/dev/null && echo "PASS: solana CLI found" || echo "NOTE: solana CLI not installed" - 常见 Solana 漏洞类别——确认已记录关键类别:
grep -q "account confusion\|signer check\|CPI\|PDA\|bump seed" SKILL.md && echo "PASS: Solana-specific vuln classes present" || echo "FAIL"
所有测试均可验证 Web3 审计准备情况。
注意事项
- 混淆 Solana 与 EVM——Solana 的编程模型(Rust、BPF)与 EVM(Solidity、EVM 字节码)完全不同。不要混用技术。
- 非 EVM 链上的重入——Solana 运行时会在一个区块内按顺序处理交易。跨程序调用(CPI)重入与 EVM 重入不同。
- Solana 账户混淆——Solana 的账户模型要求显式传递账户。缺少签名者检查和账户验证是 Solana 的主要漏洞类别。
- 任意 CPI 漏洞——将用户控制的程序 ID 传递给
invoke()或invoke_signed(),会允许调用任意程序。 - 误用 PDA(程序派生地址)——未进行适当种子验证的 PDA 可能被伪造。未经类型检查、来自用户输入的种子很危险。
相关技能与链
meme-coin-audit——当目标是 meme 币/SPL 代币,而非 DeFi 协议时使用。工作流原语:审计前的终止信号有所不同——本技能的“TVL < $500K 时跳过”不适用于 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 组件(web-frontend、管理面板、索引器 API)都应交由offensive-osint进行侦察。bb-methodology——决定是否开展深入审计时使用。工作流原语:bb-methodology的 PART 0 用于确认项目类型(web3 漏洞赏金/私人审计/智能合约审查);本技能的审计前终止信号取代该项目类型的标准评分标准。
操作说明
基于参与项目经验,并针对 2026 年对 vendored 基础内容所做的补充。 来自真实授权项目的经验,以及对本仓库 31+ 个技能领域实时测试进行的第 2 阶段验证。上游内容介绍了「是什么」; 本层介绍了「何时有效,何时无效」。
2026 年仍有收益的漏洞类别
闪电贷攻击仍是 Immunefi 上收益最高的攻击类型之一(按赏金计算,2024—2026 年排名前 5)。其经济原语——借入 5,000 万美元、操纵价格预言机、抽干资金池、偿还贷款——不断重现,因为新协议持续上线,并假设可组合性在闪电贷造成失衡时仍然成立。
重入攻击仍然有利可图,因为新协议持续上线,并使用 ERC-777/钩子/回调。不要以为这类漏洞已经绝迹——2023—2025 年的已支付漏洞库中,包含 40 多个针对采用了 Checks-Effects-Interactions 模式之后的代码库的重入漏洞(跨函数重入,以及通过在状态变更过程中调用 view 函数实现的只读重入)。
预言机操纵:仍然有丰厚回报,但难度更高。如今大多数项目都使用 Chainlink 价格馈送;攻击目标是大多数项目也使用的次级预言机(来自低流动性 Uniswap V2 交易对的 TWAP、协议自身的内部预言机,或过时的备用路径)。审计故障转移链,而不只是主馈送。
自 vendored 内容编写以来的新变化
- EIP-1153(瞬态存储)——于 2024 年引入。出现了新的重入漏洞类型:在同一笔交易中缓存的瞬态存储读取结果可能与持久化存储失步。审计任何
tload/tstore用法,检查外部调用之后的读取。 - EIP-7702(2025 年 Pectra 硬分叉)——新增了 EOA 升级为智能账户的功能。重新委托会带来类似 ATO 的新攻击原语:EOA 一次性签名后可委托给攻击者控制的合约,随后攻击者便可跨委托重放签名。
- 账户抽象(ERC-4337 bundler)——Paymaster 赞助滥用和 bundler 骚扰。未严格执行发送者允许列表的 Paymaster 合约,首次调用时就可能被耗尽资金。
- ZK-rollup 桥接漏洞——跨 rollup 重放证明、链下证明者遭入侵,以及排序器审查导致的强制纳入边界情况。
- LST/LRT 脱锚动态——流动性质押和流动性再质押代币在亏损情况下仍假定保持 1:1 锚定;预言机假定锚定,市场反映脱锚,清算逻辑随之失效。
2026 年工具栈
Foundry 仍是测试框架。使用 forge test --gas-report --debug 进行不变量测试;使用 forge fuzz 进行基于属性的测试;使用 forge inspect 审计存储布局。结合 Slither 和 Echidna 进行静态分析与模糊测试。使用 Mythril 对较小的合约进行符号执行。使用 tenderly.co 进行分叉和模拟(这是复现主网状态下攻击体验最佳的工具)。
对于 Solana:使用 anchor 框架、sealevel-attacks 漏洞库(由 anchor 维护者整理的 PoC)和 soteria-sec/sec3 扫描器。对于 Move(Aptos、Sui):使用 move-prover 和 aptos-cli aptos move test。
对于跨链项目:hyperlane 和 LayerZero 都有审计工具仓库;桥接漏洞需要模拟两端,而不只是其中一端。
预审阶段的止损信号何时重要
TVL 低于 50 万美元的项目不值得花时间审计,除非赏金最低金额很高。若审计公司已审计过,投资回报率就偏低,除非你能发现其遗漏的问题——查看审计报告中的范围排除部分,了解他们明确未审计的内容(预言机、治理、链下组件、前端、管理员路径)。
多签签署者超过 5 人且 timelock 超过 48 小时,意味着 rug-pull 风险较低;如果你的发现依赖于团队恶意这一假设,那么其影响较低,而且很可能会根据 Immunefi 的「中心化风险」排除条款被判定为范围外。阅读项目计划说明——大多数 Immunefi 项目都会明确降低或拒绝那些假设管理员恶意的发现。
报告规范
Immunefi 要求提供 Foundry PoC。未提交 PoC 的报告会被自动拒绝。需要手动设置(“先部署这个,再调用那个”)的 PoC 通常会被降级——PoC 应当通过一次 forge test 调用证明影响,并使用明确的 assertEq 断言被清空的余额、铸造的代币或遭破坏的状态。
严重性声明必须严格遵循 Immunefi 的严重性矩阵;不要自行编造严重性等级。矩阵根据 TVL 的直接经济损失百分比划分等级——针对 50 万美元协议的 Critical,与针对 5 亿美元协议的 Critical,奖金不同。声明 Critical 前,请先阅读该计划的具体严重性分配标准。