ERC4626 金库安全指南 v12.0
ERC4626 金库安全 v12.0
概述
本入门手册整合了在多个金库实现中发现的关键安全模式和漏洞,包括 ERC4626 金库、生息金库、类金库协议、自动赎回机制、加权池实现、跨链金库系统、多金库架构、与 AMM 集成的金库系统、CDP 金库实现、头寸操作模式、费用分配机制、资金费率套利系统、抵押贷款金库和稳定币协议。在审计新的金库协议时,请以此作为参考,以确保全面的漏洞检测。
最新更新:增加了来自 USSD 审计的全面模式,包括预言机价格反转漏洞、逻辑运算符错误、价格计算公式错误、预言机小数处理问题、Uniswap V3 特有的漏洞(slot0 操纵、基于余额的价格假设)、数学精度错误、访问控制遗漏、wrapped 资产的脱锚风险模式、抵押品会计错误、陈旧价格漏洞、熔断机制的边界情况和套利利用向量。这些新增内容显著增强了与预言机集成、数学运算、DEX 交互和稳定币特有漏洞相关的模式。
关键漏洞模式
1. 非标准代币支持问题
模式:金库假设标准的 ERC20 行为,而没有考虑到转账税、rebase 或其他非标准代币。
存在漏洞的代码示例:
// 存在漏洞:假设转移的金额等于收到的金额
token.safeTransferFrom(msg.sender, address(this), amount);
deposits.push(Deposit(msg.sender, amount, tokenAddress)); // 对于 FOT 代币是错误的
安全实现:
uint256 balanceBefore = token.balanceOf(address(this));
token.safeTransferFrom(msg.sender, address(this), amount);
uint256 actualAmount = token.balanceOf(address(this)) - balanceBefore;
deposits.push(Deposit(msg.sender, actualAmount, tokenAddress));
检测启发法:
- 查找在状态更新中直接使用转移金额
- 检查是否计算了余额差异
- 搜索关于代币行为的假设
- 验证以下处理:FOT 代币、rebase 代币、带有Hook的代币 (ERC777)
- 检查是否有超过 18 位小数的代币
- 验证 DAI 许可处理(非标准签名)
- 检查可以阻止转账的黑名单代币(USDT、USDC)
- 验证对零转账时会回滚的代币(LEND)的支持
- 检查清算定价中的正确小数处理
- 在 depositGlp() 场景中处理转账税代币
- 验证金库内部核算是否与实际代币余额匹配 (PoolTogether M-01)
- 检查像 BNB 这样的代币,零批准时会回滚 (Silo M-03)
2. CEI 模式违规
模式:在状态更新之前进行外部调用,从而实现重入攻击。
存在漏洞的代码示例:
// 存在漏洞:在状态更新之前转移
token.safeTransferFrom(msg.sender, address(this), amount);
deposits.push(Deposit(msg.sender, amount, tokenAddress));
安全实现:
// 首先更新状态
deposits.push(Deposit(msg.sender, 0, tokenAddress));
uint256 index = deposits.length - 1;
// 然后进行外部调用
token.safeTransferFrom(msg.sender, address(this), amount);
deposits[index].amount = amount;
检测启发法:
- 识别所有外部调用
- 检查状态更改是否发生在外部调用之后
- 寻找潜在的重入向量
- 考虑只读重入风险
- 留意可以重入的原生 ETH 转账
- 检查 ERC777 代币回调重入
- 验证Hook实现不会启用重入 (PoolTogether M-02)
3. 首次存款人攻击 (ERC4626 特有)
模式:攻击者通过成为第一个存款少量金额的存款人来操纵份额价格,然后直接捐赠代币。
攻击场景:
- 攻击者存入 1 wei 获得 1 份份额
- 攻击者直接向金库存入大量金额
- 由于四舍五入,后续存款人获得 0 份份额
存在漏洞的代码示例 (Astaria):
// ERC4626Cloned 在首次存款时具有不一致的 deposit/mint 逻辑
function previewDeposit(uint256 assets) public view virtual returns (uint256) {
return convertToShares(assets);
}
function previewMint(uint256 shares) public view virtual returns (uint256) {
uint256 supply = totalSupply();
return supply == 0 ? 10e18 : shares.mulDivUp(totalAssets(), supply);
}
缓解措施:
- 虚拟份额/资产
- 最低存款要求
- 协议的初始存款
- 无效份额(如 Uniswap V2)
- deposit 和 mint 函数之间的一致逻辑
- 设置虚拟资产等于虚拟份额 (Silo M-06)
4. 份额价格操纵
模式:攻击者通过捐赠或复杂的交互来操纵汇率。
检测:
- 检查直接向金库的代币转账
- 验证份额计算逻辑
- 寻找四舍五入漏洞
- 分析三明治攻击的可能性
- 检查最小存款绕过漏洞
- 验证汇率实际上可以增加 (PoolTogether H-01)
5. 通货膨胀/紧缩攻击
模式:操纵份额价格以窃取资金或导致资金损失。
有风险的场景:
- 没有初始份额的金库
- 低流动性金库
- 接受直接转移的金库
- 在 deposit/mint 和 withdraw/redeem 之间四舍五入不一致
- 汇率受缺陷逻辑限制 (PoolTogether H-01)
- 市场四舍五入漏洞导致份额紧缩 (Silo M-06)
6. 状态排序问题
模式:以错误的顺序调用函数导致不正确的行为。
示例:
// 错误:在提取資金之前调用 _refreshiBGT
_refreshiBGT(amount);
SafeTransferLib.safeTransferFrom(ibgt, msg.sender, address(this), amount);
// 正确:首先提取資金
SafeTransferLib.safeTransferFrom(ibgt, msg.sender, address(this), amount);
_refreshiBGT(amount);
7. ETH 转账方法问题
模式:使用带有固定 2300 gas 限制的 transfer() 或 send()。
存在漏洞的代码:
// 存在漏洞:智能合约钱包可能会失败
recipient.transfer(amount);
安全实现:
(bool success, ) = recipient.call{value: amount}("");
require(success, "ETH transfer failed");
8. 签名重放漏洞
模式:不正确的 nonce 处理允许签名重用。
存在漏洞的代码示例 (Astaria):
// 存在漏洞:相同的 commitment 可以被多次使用
function _validateCommitment(IAstariaRouter.Commitment calldata params, address receiver) internal view {
// 仅验证签名,不防止重放
address recovered = ecrecover(
keccak256(_encodeStrategyData(s, params.lienRequest.strategy, params.lienRequest.merkle.root)),
params.lienRequest.v,
params.lienRequest.r,
params.lienRequest.s
);
}
安全实现:
mapping(address => uint256) public nonces;
mapping(bytes32 => bool) public usedSignatures;
function withdraw(uint256 amount, uint256 nonce, bytes signature) {
require(nonce == nonces[msg.sender], "Invalid nonce");
bytes32 hash = keccak256(abi.encode(msg.sender, amount, nonce));
require(!usedSignatures[hash], "Signature already used");
// ... 验证签名 ...
usedSignatures[hash] = true;
nonces[msg.sender]++;
}
9. 利息/奖励计算问题
模式:不正确的利息累积或奖励分配逻辑。
常见问题:
- 在计算之前未更新状态
- 随着时间推移累积的四舍五入误差
- 计算中使用了错误的变量
- 整数溢出/下溢
- 声明单利时计算复利
- 策略师奖励基于贷款金额而不是支付金额计算
- 未正确处理动态排放率
- 由于小额头寸的四舍五入导致奖励丢失
- 在奖励分配后铸造的费用份额 (Silo M-05)
- 在声明奖励之前缺少 totalSupply 同步 (Silo M-02)
10. 跨函数重入
模式:通过共享状态的多个函数重入。
示例:
function redeemYield(uint256 amount) external {
// 烧毁 YT 代币
YieldToken(yt).burnYT(msg.sender, amount);
// 计算并发送奖励(重入点)
for(uint i; i < yieldTokens.length; i++) {
SafeTransferLib.safeTransfer(yieldTokens[i], msg.sender, claimable);
}
}
11. 访问控制问题
模式:关键函数上缺少或不正确的访问控制。
示例:
- 应该是仅所有者但不是的函数
- 不正确的修饰符用法
- 缺少调用者身份验证
- 闪电贷操作回调缺少发起者验证
- 任何人都可以调用的清算函数
- 允许 MEV 利用的公共复合函数
- 任何人都可以将收益费用铸造给任意接收者 (PoolTogether H-04)
- 抽奖管理可以被抢跑并由攻击者设置 (PoolTogether M-06)
- 在 mint/burn 函数上缺少访问控制 (USSD H-8)
12. 小数精度问题
模式:未正确处理具有不同小数位的代币。
存在漏洞的代码示例 (Astaria):
// 非 18 位小数资产的错误起始价格
listedOrder = s.COLLATERAL_TOKEN.auctionVault(
ICollateralToken.AuctionVaultParams({
settlementToken: stack[position].lien.token,
collateralId: stack[position].lien.collateralId,
maxDuration: auctionWindowMax,
startingPrice: stack[0].lien.details.liquidationInitialAsk, // 假设 18 位小数
endingPrice: 1_000 wei
})
);
安全方法:
- 始终标准化为标准精度(例如,18 位小数)
- 明确关于小数转换
- 使用各种小数位的代币进行测试
- 仔细处理超过 18 位小数的代币
- 在 minDepositAmount 计算中考虑小数位不匹配
- 永远不要假设预言机小数位 (USSD H-4)
13. 清算和坏账处理
模式:未正确处理水下头寸或坏账。
关键检查:
- 确保正确设置清算激励
- 处理抵押品价值 < 债务的情况
- 防止清算恶意行为
- 确保清算不能被回滚转移阻止
- 在债务计算中考虑清算人奖励
- 防止自我清算漏洞
- 在 lien 开放时处理 epoch
- 验证清算是否可以在借款人违约之前发生
- 检查借款人是否可以被清算
- 确保债务不能在未完全偿还的情况下关闭
- 防止在暂停还款时进行清算
- 处理代币禁用对现有头寸的影响
- 在恢复还款后提供宽限期
- 验证清算人还款金额
- 防止无限贷款展期
- 避免将还款发送到零地址
- 确保借款人始终可以偿还贷款
- 在多个贷款中完全计入还款
- 激励小额头寸的清算
- 确保清算改善健康评分
14. 预言机和价格馈送问题
模式:价格馈送集成中的漏洞。
安全措施:
- 使用多个预言机来源
- 实施陈旧性检查
- 添加价格偏差限制
- 优雅地处理预言机故障
- 检查 L2 上的排序器运行时间
- 验证 roundId,价格 > 0 和时间戳
- 考虑市场动荡期间的预言机操纵
- 检查反向基础/利率代币 (USSD H-1)
- 验证预言机小数位假设 (USSD H-4)
- 始终检查陈旧价格 (USSD M-1)
- 处理熔断机制的最小/最大价格 (USSD M-7)
- 确保预言机单位与预期面额匹配 (USSD H-11)
15. 升级和迁移风险
模式:升级金库实现或迁移资金时出现的问题。
考虑因素:
- 存储布局保留
- 正确初始化新变量
- 迁移函数安全性
- 升级期间的暂停机制
- 阻止迁移的 vGMX/vGLP 代币的存在
16. 自转移漏洞
模式:未正确处理自转移的函数,允许无限积分/奖励。
存在漏洞的代码示例 (AllocationVesting):
// 存在漏洞:未正确处理自转移
allocations[from].points = uint24(fromAllocation.points - points);
allocations[to].points = toAllocation.points + uint24(points);
缓解措施:
error SelfTransfer();
if(from == to) revert SelfTransfer();
17. 奖励领取前缺少状态更新
模式:在领取奖励之前未能更新积分状态,导致累积奖励丢失。
存在漏洞的模式:
// 存在漏洞:在 _fetchRewards 之前缺少 _updateIntegrals
function fetchRewards() external {
_fetchRewards(); // 更新 lastUpdate 而不捕获挂起的奖励
}
安全实现:
function fetchRewards() external {
_updateIntegrals(address(0), 0, totalSupply);
_fetchRewards();
}
18. 单个借款人清算失败
模式:清算逻辑在仅存在一个借款人时失败。
存在漏洞的代码:
// 存在漏洞:当 troveCount == 1 时跳过清算
while (trovesRemaining > 0 && troveCount > 1) {
// 清算逻辑
}
影响:无法清算最后一个借款人,在 sunsetting 期间尤为关键。
19. 来自禁用接收器的代币丢失
模式:当禁用的 emissions 接收器没有声明分配的 emissions 时,永久丢失的代币。
存在漏洞的流程:
- 接收器获得投票分配
- 接收器被禁用
- 如果接收器没有调用
allocateNewEmissions
,代币将永远丢失
缓解措施:允许任何人为禁用的接收器调用 allocateNewEmissions
。
20. 关键函数中的向下转换溢出
模式:不安全的向下转换导致用户资金损失。
存在漏洞的代码:
struct AccountData {
uint32 locked; // 危险:可以用大量存款溢出
}
accountData.locked = uint32(accountData.locked + _amount);
缓解措施:使用 SafeCast 并强制执行不变量:
require(totalSupply <= type(uint32).max * lockToTokenRatio);
21. 预声明限制绕过
模式:vesting 限制可以通过积分转移绕过。
攻击流程:
- 用户预先声明允许的最大值
- 将积分转移到新地址
- 新地址有 0 个预声明,可以再次预声明
缓解措施:按比例转移预先声明的金额。
22. 抵押品收益双重声明
模式:缺少状态更新允许多次声明相同的抵押品收益。
存在漏洞的模式:
// 存在漏洞:未调用 _accrueDepositorCollateralGain
function claimCollateralGains(address recipient, uint256[] calldata collateralIndexes) external {
// 直接声明,无需先累积
}
23. 奖励分配中的精度损失
模式:乘法之前的除法导致奖励永久损失。
存在漏洞的模式:
// 首先除以总权重
uint256 votePct = receiverWeight / totalWeight;
// 然后乘以排放量
uint256 amount = votePct * weeklyEmissions;
缓解措施:避免中间除法:
uint256 amount = (weeklyEmissions * receiverWeight) / totalWeight;
24. 数组长度限制
模式:固定大小的数组在超过限制时会导致 panic 回滚。
存在漏洞的代码:
mapping(address => uint256[256] deposits) public depositSums;
// 如果超过 256 个抵押品,则 Panic
缓解措施:对数组增长添加显式检查和限制。
25. 提款中的 Dust 处理
模式:当 Dust 四舍五入导致零剩余余额时,状态更新不正确。
影响:存储不一致,系统认为用户具有余额为 0 的活动锁定。
26. 预言机小数处理错误
模式:仅适用于 8 位小数预言机的价格馈送计算。
存在漏洞的代码示例 (Y2K):
nowPrice = (price1 * 10000) / price2;
nowPrice = nowPrice * int256(10**(18 - priceFeed1.decimals()));
return nowPrice / 1000000;
影响:
- 对于 6 位小数的馈送:返回 10 位小数的数字(相差 4 个数量级)
- 对于 18 位小数的馈送:返回 0 或回滚
- 仅适用于 8 位小数的馈送
缓解措施:
nowPrice = (price1 * 10000) / price2;
nowPrice = nowPrice * int256(10**(priceFeed1.decimals())) * 100;
return nowPrice / 1000000;
27. 排序器停机阻止关键操作
模式:关键函数在 L2 排序器停机期间依赖于预言机价格失败。
存在漏洞的代码示例 (Y2K):
function triggerEndEpoch(uint256 marketIndex, uint256 epochEnd) public {
// ... 逻辑 ...
emit DepegInsurance(
// ...
getLatestPrice(insrVault.tokenInsured()) // 在排序器停机期间回滚
);
}
影响:获胜者无法提取,尽管 epoch 已经结束
缓解措施:对于非关键价格使用(如事件排放),优雅地处理预言机故障
28. 没有交易对手的总损失
模式:当没有人在交易对手金库存款时,用户会损失所有存款。
存在漏洞的代码示例 (Y2K):
function triggerDepeg(uint256 marketIndex, uint256 epochEnd) public {
// 如果只有对冲金库存款,则风险金库为 0
insrVault.setClaimTVL(epochEnd, riskVault.idFinalTVL(epochEnd)); // 设置为 0
riskVault.setClaimTVL(epochEnd, insrVault.idFinalTVL(epochEnd));
insrVault.sendTokens(epochEnd, address(riskVault)); // 将所有发送到风险金库
riskVault.sendTokens(epochEnd, address(insrVault)); // 不发回任何东西
}
影响:单边市场中用户的存款完全损失
缓解措施:当不存在交易对手时允许完全提款
29. 基于批准的提款恶意行为
模式:不正确的批准检查允许任何人强制提款。
存在漏洞的代码示例 (Y2K):
function withdraw(uint256 id, uint256 assets, address receiver, address owner) external {
if(msg.sender != owner && isApprovedForAll(owner, receiver) == false)
revert OwnerDidNotAuthorize(msg.sender, owner);
// 如果 receiver 被批准,任何人都可以提款!
}
影响:攻击者可以强迫获胜者在不合时宜的时候提款
缓解措施:检查 msg.sender 的批准,而不是 receiver:
if(msg.sender != owner && isApprovedForAll(owner, msg.sender) == false)
revert OwnerDidNotAuthorize(msg.sender, owner);
30. 上涨脱锚触发保险
模式:当Hook资产的价值高于基础资产时,保险赔付。
存在漏洞的代码示例 (Y2K):
if (price1 > price2) {
nowPrice = (price2 * 10000) / price1;
} else {
nowPrice = (price1 * 10000) / price2;
}
// 计算较低价格的比率,在资产升值时触发脱锚
影响:风险用户必须在不应该支付时支付(资产升值是积极的)
缓解措施:始终计算比率为Hook/基础,而不是最小/最大
31. 协议特定的提款参数不匹配
模式:关于协议特定的提款函数的不正确假设。
存在漏洞的代码示例 (Swivel):
// 假设所有协议都使用基础金额进行提款
return IYearnVault(c).withdraw(a) >= 0;
// 但 Yearn 的 withdraw() 采用份额,而不是资产!
影响:
- 如果份额不足:交易回滚
- 如果份额过多:提取的资产多于预期,资金被锁定
缓解措施:
uint256 pricePerShare = IYearnVault(c).pricePerShare();
return IYearnVault(c).withdraw(a / pricePerShare) >= 0;
32. 到期后 VaultTracker 状态不一致
模式:汇率可能超过到期汇率,导致后续操作中的下溢。
存在漏洞的代码示例 (Swivel):
function removeNotional(address o, uint256 a) external {
uint256 exchangeRate = Compounding.exchangeRate(protocol, cTokenAddr);
// 到期后,exchangeRate > maturityRate
if (maturityRate > 0) {
yield = ((maturityRate * 1e26) / vlt.exchangeRate) - 1e26; // 下溢!
}
}
影响:用户在到期后无法提款或索取利息
缓解措施:
vlt.exchangeRate = (maturityRate > 0 && maturityRate < exchangeRate) ? maturityRate : exchangeRate;
33. 接口定义导致函数调用失败
模式:调用者和实现之间的接口不匹配。
存在漏洞的代码示例 (Swivel):
// MarketPlace 调用:
ISwivel(swivel).authRedeem(p, u, market.cTokenAddr, t, a);
// 但 Swivel 只有:
function authRedeemZcToken(uint8 p, address u, address c, address t, uint256 a) external
影响:关键函数永久失败,锁定用户资金
缓解措施:确保接口定义与实现匹配
34. 复利计算缺少应计利息
模式:利息计算忽略了先前应计的可赎回金额。
存在漏洞的代码示例 (Swivel):
function addNotional(address o, uint256 a) external {
uint256 yield = ((exchangeRate * 1e26) / vlt.exchangeRate) - 1e26;
uint256 interest = (yield * vlt.notional) / 1e26; // 仅使用名义本金!
// 应该使用 vlt.notional + vlt.redeemable
}
影响:随着时间的推移,用户获得的收益少于应得的收益
缓解措施:在收益计算中包括可赎回金额
35. 乘法之前除法导致资金损失
模式:数学运算顺序不正确导致精度损失。
来自 Dacian 研究的增强模式: Solidity 中的除法向下舍入,因此为了最大限度地减少舍入误差,始终在除法之前执行乘法。
存在漏洞的代码示例 (Y2K):
// 在 beforeWithdraw 中:
entitledAmount = amount.divWadDown(idFinalTVL[id]).mulDivDown(idClaimTVL[id], 1 ether);
// 对于小额可以返回 0
其他示例 (Numeon):
// 乘法之前的除法导致精度损失
uint256 scale0 = Math.mulDiv(amount0, 1e18, liquidity) * token0Scale;
uint256 scale1 = Math.mulDiv(amount1, 1e18, liquidity) * token1Scale;
其他示例 (USSD):
// 存在漏洞:额外除以 1e18 会导致大量精度损失
uint256 amountToSellUnits = IERC20Upgradeable(collateral[i].token).balanceOf(USSD) *
((amountToBuyLeftUSD * 1e18 / collateralval) / 1e18) / 1e18;
影响:用户可以调用 withdraw 并收到 0 个代币;计算中存在显着的精度损失
缓解措施:在除法之前乘法:
entitledAmount = (amount * idClaimTVL[id]) / idFinalTVL[id];
// 或者对于 Numeon:
uint256 scale0 = Math.mulDiv(amount0 * token0Scale, 1e18, liquidity);
uint256 scale1 = Math.mulDiv(amount1 * token1Scale, 1e18, liquidity);
高级检测:展开函数调用以显示隐藏的乘法之前的除法:
// iRate = baseVbr + utilRate.wmul(slope1).wdiv(optimalUsageRate)
// 展开为:baseVbr + utilRate * (slope1 / 1e18) * (1e18 / optimalUsageRate)
// 修复:iRate = baseVbr + utilRate * slope1 / optimalUsageRate;
36. 接受陈旧的预言机价格
模式:接受时间戳 = 0 或非常旧的时间戳的预言机价格。
存在漏洞的代码示例 (Y2K):
function getLatestPrice(address _token) public view returns (int256 nowPrice) {
// ...
if(timeStamp == 0) // 应该检查陈旧性,而不仅仅是 0
revert TimestampZero();
return price;
}
影响:协议以过时的价格运行
缓解措施:
uint constant observationFrequency = 1 hours;
if(timeStamp < block.timestamp - uint256(observationFrequency))
revert StalePrice();
37. 基于精确执行价格的脱锚触发
模式:当价格等于执行价格时触发脱锚事件,而不仅仅是低于。
存在漏洞的代码示例 (Y2K):
modifier isDisaster(uint256 marketIndex, uint256 epochEnd) {
if(vault.strikePrice() < getLatestPrice(vault.tokenInsured()))
revert PriceNotAtStrikePrice();
// 允许在价格 = 执行价格时脱锚
_;
}
影响:保险事件触发不正确
缓解措施:使用 <=
而不是 <
38. 奖励代币恢复后门
模式:管理员可以提取应该分配给用户的奖励代币。
存在漏洞的代码示例 (Y2K):
function recoverERC20(address tokenAddress, uint256 tokenAmount) external onlyOwner {
require(tokenAddress != address(stakingToken), "无法提取 staking 代币");
// 缺少:require(tokenAddress != address(rewardsToken))
ERC20(tokenAddress).safeTransfer(owner, tokenAmount);
}
影响:管理员可以 rug pull 奖励代币
缓解措施:防止提取 staking 和奖励代币
39. 奖励率稀释攻击
模式:通知奖励金额为 0 以稀释奖励率。
存在漏洞的代码示例 (Y2K):
function notifyRewardAmount(uint256 reward) external {
if (block.timestamp >= periodFinish) {
rewardRate = reward.div(rewardsDuration);
} else {
uint256 remaining = periodFinish.sub(block.timestamp);
uint256 leftover = remaining.mul(rewardRate);
rewardRate = reward.add(leftover).div(rewardsDuration); // 被 0 稀释
}
}
影响:奖励率可以重复降低 20%
缓解措施:防止每次调用延长持续时间或保持恒定率
40. 过期的金库代币赚取奖励
模式:毫无价值的过期代币继续赚取奖励。
存在漏洞的代码示例 (Y2K):
// 在 triggerEndEpoch 之后:
insrVault.setClaimTVL(epochEnd, 0); // 使代币毫无价值
// 但 StakingRewards 不知道并且继续奖励
影响:从未来有效 epoch 窃取奖励
缓解措施:在 StakingRewards 中添加到期验证
生息金库特定的模式
41. 生息头寸清算绕过
模式:存入生息头寸(如 Gamma Hypervisor)的抵押品不受清算影响。
存在漏洞的代码:
function liquidate() external onlyVaultManager {
// 仅清算常规抵押品,而不是生息头寸
for (uint256 i = 0; i < tokens.length; i++) {
if (tokens[i].symbol != NATIVE) liquidateERC20(IERC20(tokens[i].addr));
}
// 不包括 Hypervisor 代币!
}
影响:用户可以在生息头寸中拥有抵押品,被清算,仍然可以提取生息抵押品。
42. 直接移除生息头寸代币
模式:可以移除生息头寸代币(如 Hypervisor 份额),而无需进行抵押品检查。
存在漏洞的代码:
function removeAsset(address _tokenAddr, uint256 _amount, address _to) external onlyOwner {
ITokenManager.Token memory token = getTokenManager().getTokenIfExists(_tokenAddr);
if (token.addr == _tokenAddr && !canRemoveCollateral(token, _amount)) revert Undercollateralised();
// Hypervisor 代币不在 TokenManager 中,因此绕过检查!
IERC20(_tokenAddr).safeTransfer(_to, _amount);
}
43. 自支持稳定币问题
模式:通过 LP 头寸支持自己的稳定币会产生系统性风险。
示例:USDs/USDC 池,其中 USDs 计为 USDs 贷款的抵押品。
影响:
- 脱钩事件期间的死亡螺旋
- 打破了维持Hook的经济激励
- 最多可能支持 50%
44. 硬编码的稳定币价格假设
模式:假设 USD 稳定币始终等于 1 美元。
存在漏洞的代码:
if (_token0 == address(USDs) || _token1 == address(USDs)) {
// 假设两个代币 = 1 美元
_usds += _underlying0 * 10 ** (18 - ERC20(_token0).decimals());
_usds += _underlying1 * 10 ** (18 - ERC20(_token1).decimals());
}
影响:在脱钩事件期间过度抵押。
45. 过度的滑点容忍度
模式:允许在收益存款/提款中损失高达 10%。
存在漏洞的代码:
function significantCollateralDrop(uint256 _pre, uint256 _post) private pure returns (bool) {
return _post < 9 * _pre / 10; // 接受 10% 的损失!
}
46. 硬编码的 DEX 池费用
模式:固定的池费用阻止了最佳路由。
存在漏洞的代码:
fee: 3000, // 始终使用 0.3% 的池
影响:更高的滑点,失败的交换,潜在的收益功能 DoS。
47. 收益头寸数据移除问题
模式:管理员移除 Hypervisor 数据会锁定用户资金。
存在漏洞的流程:
- 用户存入 Hypervisor
- 管理员调用
removeHypervisorData
- 用户无法提款 - 资金被锁定
48. 代币符号与地址混淆
模式:原生代币与 WETH 的处理不一致。
问题:ETH 和 WETH 符号都映射到 WETH 地址,导致交换失败。
自动赎回特定的模式
49. Hypervisor 抵押品赎回滑点问题
模式:Hypervisor 抵押品的自动赎回在提款和重新存入期间缺乏滑点保护。
存在漏洞的代码:
// 提款中没有滑点保护**缓解措施**:
- 在构造函数中预填充映射
- 添加带有访问控制的 setter 函数
- 从外部合约查询
#### 52. 关键功能上的访问控制不足
**模式**: 任何人都可以调用的外部函数,从而导致恶意攻击。
**有漏洞的代码**:
```solidity
function performUpkeep(bytes calldata performData) external {
// 没有访问控制 - 任何人都可以调用!
if (lastRequestId == bytes32(0)) {
triggerRequest();
}
}
攻击向量:
- 不管价格如何,重复触发赎回
- 发送 USDs 到 vault 中,导致履行时的下溢
- 耗尽 Chainlink 订阅资金
缓解措施:
- 添加 Chainlink Automation forwarder 访问控制
- 重新检查触发条件
- 使用余额差异而不是直接使用 balanceOf
53. Fulfilment Revert DoS
模式: 履行中的任何 revert 都会永久禁用自动赎回。
有漏洞的模式:
function fulfillRequest(bytes32 requestId, bytes memory response, bytes memory err) internal {
// 此处的任何 revert 都会永久设置 lastRequestId != 0
// ... 冒险操作 ...
lastRequestId = bytes32(0); // 在 revert 时永远不会到达
}
影响: 永久 DoS 需要重新部署。
缓解措施:
- 永远不允许 fulfillRequest revert
- 验证响应长度/格式
- 对外部调用使用 try/catch
- 添加管理员重置功能
54. 通过瞬时价格进行 Oracle 操纵
模式: 使用现货价格而不是 TWAP 作为触发条件。
有漏洞的代码:
function checkUpkeep() external returns (bool upkeepNeeded, bytes memory) {
(uint160 sqrtPriceX96,,,,,,) = pool.slot0(); // 现货价格!
upkeepNeeded = sqrtPriceX96 <= triggerPrice;
}
影响:
- 通过闪电贷操纵强制自动赎回
- MEV 机器人可以通过 JIT 流动性抢先交易
- Vault 所有者可以强制偿还债务,避免费用
缓解措施:
- 使用 15-30 分钟间隔的 TWAP
- 在 performUpkeep 中重新检查条件
- 添加 Chainlink 价格 oracle 集成
55. 不安全的存在符号-无符号转换
模式: 将带符号的流动性值转换为无符号,而不检查符号。
有漏洞的代码:
(, int128 _liquidityNet,,,,,,) = pool.ticks(_lowerTick);
_liquidity += uint128(_liquidityNet); // 如果为负数,则可能下溢!
影响:
- 大量高估所需的USDs
- 完全的金库赎回
- 可能导致 DoS 的 revert
缓解措施:
if (_liquidityNet >= 0) {
_liquidity = _liquidity + uint128(_liquidityNet);
} else {
_liquidity = _liquidity - uint128(-_liquidityNet);
}
56. 集中流动性 Tick 计算错误
模式: 正 tick 的不正确的 tick 范围计算。
有漏洞的代码:
// 由于舍入,对于非倍数的正值 tick 错误
int24 _upperTick = _tick / _spacing * _spacing;
int24 _lowerTick = _upperTick - _spacing;
影响: 不正确的 USDs 计算,尽管由于小数差异不太可能。
缓解措施: 在范围计算中考虑 tick 符号。
57. 缺少响应验证
模式: 未验证 Chainlink Function 的响应数据。
缺失的检查:
- Token 是有效的抵押品或 Hypervisor
- TokenID 存在并且已铸造
- Vault 地址非零
- 响应长度与预期格式匹配
缓解措施: 在使用响应数据之前添加全面的验证。
58. 增强的 Hypervisor 滑点漏洞
模式: 具有收益头寸的自动赎回缺乏对提款、交换和再存入操作的全面滑点保护。
有漏洞的代码 (The Standard v2):
// quickWithdraw 没有滑点保护
IHypervisor(_hypervisor).withdraw(
_thisBalanceOf(_hypervisor), address(this), address(this),
[uint256(0), uint256(0), uint256(0), uint256(0)]
);
// 具有 amountOutMinimum: 0 的多个交换操作
ISwapRouter(uniswapRouter).exactInputSingle(
ISwapRouter.ExactInputSingleParams({
amountOutMinimum: 0,
sqrtPriceLimitX96: 0
...
})
);
攻击场景:
- 攻击者监控内存池中的自动赎回交易
- sandwich 提款/交换/再存入操作
- 由于累积的滑点,Vault 变得抵押不足
缓解措施:
- 在赎回之前和之后实施抵押品百分比检查
- 添加最低抵押品保留要求
- 在整个流程中使用适当的滑点参数
59. 不正确的功能参数路由
模式: 在复杂的调用链中将错误的地址传递给关键函数。
有漏洞的代码 (The Standard):
// 错误:传递 vault 地址而不是 swap router
IRedeemable(_smartVault).autoRedemption(
_smartVault, // 应该是 swapRouter!
quoter,
_token,
_collateralToUSDCPath,
_USDsTargetAmount,
_hypervisor
);
影响: 当错误的合约收到调用时,功能完全失败。
检测: 跟踪通过调用链的所有参数传递,尤其是在自动赎回流程中。
60. 基于余额的赎回金额漏洞
模式: 使用直接的 balanceOf()
来计算赎回金额,而不是跟踪实际交换金额。
有漏洞的代码:
// 有漏洞:可以通过直接转账来操纵
uint256 _usdsBalance = USDs.balanceOf(address(this));
minted -= _usdsBalance;
USDs.burn(address(this), _usdsBalance);
攻击: 直接发送 USDs 到 vault 中,导致下溢和 DoS 自动赎回。
缓解措施:
// 使用交换输出量
uint256 amountOut = ISwapRouter(router).exactInput(params);
minted -= amountOut;
USDs.burn(address(this), amountOut);
61. 抵押品百分比验证漏洞
模式: 自动赎回后,Vault 缺少维护健康抵押品的验证。
有漏洞的场景:
- Vault 已经接近清算阈值
- 赎回期间的重大滑点
- 完全偿还债务时除以零
安全实施:
function validateCollateralPercentage(uint256 minPercentage) private view {
if (minted == 0) return; // 处理零债务情况
uint256 collateralPercentage = (usdCollateral() * HUNDRED_PC) / minted;
require(collateralPercentage >= minPercentage, "Below min collateral");
}
62. 交换路径验证失败
模式: 未验证交换路径是否产生预期的输出 token。
有漏洞的示例 (The Standard):
- 交换路径配置为
collateral -> USDC
而不是collateral -> USDs
- 交换成功,但没有偿还债务
- 由于抵押品丢失,Vault 变得可以清算
缓解措施:
- 存储输入和输出路径
- 验证路径端点是否与预期的 token 匹配
- 添加交换后余额检查
63. Hypervisor-抵押品对应验证
模式: 未验证 Hypervisor token 是否与其底层抵押品 token 相对应。
有漏洞的代码:
// 没有验证 hypervisor 实际是否包含此抵押品
if (hypervisorCollaterals[_token] != address(0)) {
_hypervisor = _token;
_token = hypervisorCollaterals[_hypervisor];
}
缓解措施: 验证 hypervisor token 对:
function validHypervisorPair(address hypervisor, address collateral) private view returns (bool) {
IHypervisor hyp = IHypervisor(hypervisor);
return hyp.token0() == collateral || hyp.token1() == collateral;
}
加权池和存储优化模式
64. 打包数据中的存储槽边界错误
模式: 在处理跨越多个槽的打包存储时,不正确的索引边界检查。
有漏洞的代码 (QuantAMM):
// 错误:第二个槽应该 >= 4
if (request.indexIn > 4 && request.indexOut < 4) {
// 跨槽逻辑
} else if (tokenIndex > 4) { // 应该 >= 4
index = tokenIndex - 4;
targetWrappedToken = _normalizedSecondFourWeights;
}
影响:
- 涉及边界索引 (4) 处的 token 的交换失败
- 跨槽交换变得不可能
- 数组越界错误
缓解措施:
// 正确的边界检查
if (tokenIndex >= 4) {
index = tokenIndex - 4;
targetWrappedToken = _normalizedSecondFourWeights;
}
65. 权重乘数索引未对齐
模式: 打包存储中乘数的预期位置和实际位置之间的不匹配。
有漏洞的代码 (QuantAMM):
// 存储期望:[w1,w2,w3,w4,m1,m2,m3,m4]
// 但是代码存储:[w1,w2,w3,w4,m1,m2,0,0] 用于 6 个 token
int256 blockMultiplier = tokenWeights[tokenIndex + tokensInTokenWeights];
// 访问 token 4-7 的错误索引
影响: 不正确的权重插值导致错误的交换金额。
缓解措施: 确保所有 token 计数中的乘数定位一致。
66. 负乘数转换错误
模式: 从有符号整数转换为无符号整数时,对负值的不正确处理。
有漏洞的代码 (QuantAMM):
if (multiplier > 0) {
return uint256(weight) + FixedPoint.mulDown(uint256(multiplierScaled18), time);
} else {
// 错误:uint256(negative) 创建巨大的正数
return uint256(weight) - FixedPoint.mulUp(uint256(multiplierScaled18), time);
}
缓解措施:
return uint256(weight) - FixedPoint.mulUp(uint256(-multiplierScaled18), time);
67. Token 化索引计算错误
模式: 在跨存储槽处理 token 时,不正确的索引计算。
有漏洞的代码 (QuantAMM):
if (totalTokens > 4) {
tokenIndex = 4; // 为第一个槽设置
}
// 处理第一个槽...
if (totalTokens > 4) {
tokenIndex -= 4; // 错误:重置为 0,破坏第二个槽
}
影响: 使用错误的乘数计算 token 4-7 的权重。
68. 权限系统绕过漏洞
模式: 错误的权限检查允许未经授权访问受限函数。
有漏洞的代码 (QuantAMM):
bool internalCall = msg.sender != address(this);
// 始终为真,因为合约不会以这种方式调用自身
require(internalCall || approvedPoolActions[_pool] & MASK_POOL_GET_DATA > 0);
影响: 权限检查实际上被绕过。
缓解措施: 删除不明确的内部调用检查或正确实施自调用。
69. 范围验证逻辑错误
模式: 打包数据验证逻辑中的复制粘贴错误。
有漏洞的代码 (QuantAMM):
require(
_firstInt <= MAX32 && _firstInt >= MIN32 &&
_secondInt <= MAX32 && _secondInt >= MIN32 &&
_thirdInt <= MAX32 && _firstInt >= MIN32 && // 应该是 _thirdInt
_fourthInt <= MAX32 && _firstInt >= MIN32 && // 应该是 _fourthInt
// ... 以同样的错误继续
);
影响: 可以打包无效值,导致意外行为。
70. 后备逻辑中接受过时的 Oracle
模式: 当备份 Oracle 不可用时,接受过时的 Oracle 数据。
有漏洞的代码 (QuantAMM):
if (oracleResult.timestamp > block.timestamp - staleness) {
outputData[i] = oracleResult.data;
} else {
// 检查备份 Oracle
for (uint j = 1; j < numAssetOracles; ) {
// 如果没有备份 (numAssetOracles == 1),则跳过循环
}
outputData[i] = oracleResult.data; // 使用过时的数据!
}
缓解措施: 当没有新的 Oracle 数据可用时,进行 Revert。
71. 权重更新权限漏洞
模式: 缺少权限检查,允许未经授权更新关键时序参数。
有漏洞的代码 (QuantAMM):
if (poolRegistryEntry & MASK_POOL_DAO_WEIGHT_UPDATES > 0) {
require(msg.sender == daoRunner, "ONLYDAO");
} else if (poolRegistryEntry & MASK_POOL_OWNER_UPDATES > 0) {
require(msg.sender == poolManager, "ONLYMANAGER");
}
// 没有 else - 如果没有设置权限,任何人都可以更新!
poolRuleSettings[_poolAddress].lastPoolUpdateRun = _time;
缓解措施: 添加 else 子句以在缺少权限时进行 Revert。
72. 权重计算中的整数溢出
模式: 基于时间的计算中未检查的算术运算导致溢出。
有漏洞的代码 (QuantAMM):
if (blockMultiplier == 0) {
blockTimeUntilGuardRailHit = type(int256).max;
}
// 稍后...
currentLastInterpolationPossible += int40(uint40(block.timestamp)); // 溢出!
缓解措施: 防止溢出情况:
if(currentLastInterpolationPossible < int256(type(int40).max)) {
currentLastInterpolationPossible += int40(uint40(block.timestamp));
}
73. 极端权重比率漏洞
模式: 处理具有极端权重比率的池时出现数学溢出。
示例 (QuantAMM):
// 权重= 0.01166 (1.166%),余额= 7.5M 个 token,invariantRatio = 3.0
newBalance = oldBalance * (invariantRatio ^ (1/weight))
= 7500e21 * (3.0 ^ 85.76)
= OVERFLOW
影响: 不平衡流动性操作的 DoS。
缓解措施: 强制执行更高的最低权重阈值(例如,3% 而不是 0.1%)。
74. 跨链 Token 转移价值损失 (ERC4626 + LayerZero)
模式: ERC4626 vault 实施通过 mint/burn 方法而不是 lock/unlock 的跨链转移,导致份额价值扭曲。
有漏洞的代码示例 (D2):
function _debit(address _from, uint256 _amountLD, uint256 _minAmountLD, uint32 _dstEid) internal virtual override returns (uint256 amountSentLD, uint256 amountReceivedLD) {
(amountSentLD, amountReceivedLD) = _debitView(_amountLD, _minAmountLD, _dstEid);
_burn(_from, amountSentLD); // 有漏洞:减少 totalSupply,增加份额价值
}
影响:
- 如果其他人在转移过程中提款,则跨链转移 token 的用户会损失价值
- 份额价格操纵机会
- 可能完全损失资金
缓解措施:
// 使用 lock/unlock 方法
function _debit(address _from, uint256 _amountLD, uint256 _minAmountLD, uint32 _dstEid) internal virtual override returns (uint256 amountSentLD, uint256 amountReceivedLD) {
(amountSentLD, amountReceivedLD) = _debitView(_amountLD, _minAmountLD, _dstEid);
_transfer(_from, address(this), amountSentLD); // 锁定 token 而不是销毁
}
75. 外部协议集成缺少回调实现
模式: 将合约设置为回调接收器,而不实现所需的接口,导致交易 revert。
有漏洞的代码示例 (D2 GMXV2):
function gmxv2_withdraw(...) external {
IExchangeRouter.CreateWithdrawalParams memory params = IExchangeRouter.CreateWithdrawalParams({
receiver: address(this),
callbackContract: address(this), // 有漏洞:没有回调实现
// ...
});
}
影响: 功能完全丧失,资金锁定在协议中
缓解措施: 要么实现所需的回调 要么设置为零地址
76. 没有验证的不安全 Token 转移
模式: 使用标准的 ERC20 transfer/transferFrom,而不检查返回值。
有漏洞的代码示例 (D2 VaultV3):
function custodyFunds() external onlyTrader notCustodied duringEpoch returns (uint256) {
custodied = true;
custodiedAmount = amount;
IERC20(asset()).transfer(trader, amount); // 有漏洞:没有成功检查
emit FundsCustodied(epochId, amount);
}
影响: 如果转移静默失败,则状态不一致
缓解措施: 使用 SafeERC20 库
77. DeFi 集成中缺少滑点保护
模式: 交换/流动性操作中硬编码的零滑点参数。
有漏洞的代码示例 (D2 Pendle):
router.addLiquiditySingleToken(
address(this),
address(market),
0, // 有漏洞:minLpOut 硬编码为 0
approxParams,
input,
limitOrderData
);
影响: MEV 利用,三明治攻击,价值提取
缓解措施: 允许可配置的滑点参数
78. 不正确的外部协议接口
模式: 为外部协议调用使用错误的函数签名。
有漏洞的代码示例 (D2 Berachain):
// 合约使用:
function getReward(address account) external;
// 但是实际接口是:
function getReward(address account, address recipient) external;
影响: 功能完全失败,无法声明奖励
79. 链 ID 验证错误
模式: 不正确的链 ID 阻止了正确的部署配置。
有漏洞的代码示例 (D2):
} else if (block.chainid == 80000) { // 错误:Berachain 是 80094
影响: 部署了错误的配置,缺少功能
80. 交换中缺少输出 Token 验证
模式: 在交换操作中,仅验证输入 token 而不验证输出 token。
有漏洞的代码示例 (D2 Kodiak):
function bera_kodiakv2_swap(address token, uint amount, uint amountMin, address[] calldata path) external {
validateToken(token); // 仅验证输入
// 缺少:validateToken(path[path.length-1])
}
影响: 如果交换为未经批准的资产,则 token 可能会永久卡住
81. 不正确的访问控制权限分配
模式: 将管理角色授予不应该拥有的运营帐户。
有漏洞的代码示例 (D2):
s.grantRole(ADMIN_ROLE, args._trader); // 危险:交易者可以撤销所有者的访问权限
s.grantRole(EXECUTOR_ROLE, args._trader);
影响: 受损的交易者可以锁定合法的管理员
82. DEX 操作中缺少截止时间保护
模式: 在时间敏感的操作中使用无限截止时间或没有截止时间。
有漏洞的代码示例 (D2):
kodiakv2.addLiquidity(..., type(uint256).max); // 无限截止时间
影响: 交易可以在意外的时间执行,MEV 利用
83. 不正确的批准目标
模式: 批准错误的合约进行 token 操作。
有漏洞的代码示例 (D2 Silo):
function silo_execute(ISiloRouter.Action[] calldata actions) external {
IERC20(actions[0].asset).approve(actions[0].silo, actions[0].amount); // 应该批准路由器
router.execute(actions);
}
84. 跨协议缺少 LTV 检查
模式: 在集成多个借贷协议时,风险管理不一致。
有漏洞的代码示例 (D2):
// Aave 有 LTV 检查:
require(totalDebtBase <= maxDebtBase, "borrow amount exceeds max LTV");
// 但是 Silo 和 Dolomite 缺少此检查
影响: 更高的清算风险,不一致的风险概况
85. 借用未经授权的 Token
模式: 允许借用绕过交易限制的 token。
影响: 破坏资产控制机制
86. 无需许可的外部奖励声明导致资金锁定
模式: 外部协议允许任何人代表其他用户声明奖励,从而绕过 vault 的奖励处理逻辑。
有漏洞的代码示例 (Dolomite):
// 外部协议允许无需许可的声明
function getRewardForUser(address _user) public {
// 任何人都可以对任何用户调用此函数
rewards[_user][_rewardsToken] = 0;
_rewardsToken.transfer(_user, reward);
}
// vault 期望通过其自身的逻辑来处理奖励
function _performDepositRewardByRewardType(...) internal {
// 当从外部声明奖励时,此逻辑被绕过
}
影响:
- 奖励直接发送到 vault 地址,而没有得到适当的处理
- Token 卡在合约中,而没有被质押或分配
- 需要升级才能恢复资金
缓解措施:
function _performDepositRewardByRewardType(...) internal {
// 将现有余额添加到奖励金额
_amount += IERC20(token).balanceOf(address(this));
}
87. 具有自动重新质押的反直觉退出行为
模式: 退出函数声称要完全退出,但会自动将奖励重新质押,从而使用户部分投资。
有漏洞的代码示例 (Dolomite):
function _exit() internal {
vault.exit(); // 取消质押原始存款
_handleRewards(rewards); // 但是重新质押奖励!
}
function _handleRewards(UserReward[] memory _rewards) internal {
if (_rewards[i].token == UNDERLYING_TOKEN()) {
// 自动重新质押 iBGT 奖励
factory.depositIntoDolomiteMargin(DEFAULT_ACCOUNT_NUMBER, _rewards[i].amount);
}
}
影响:
- 用户认为他们已经完全退出,但仍然有质押头寸
- 当“退出”并不意味着完全退出时,用户体验不佳
- 奖励可能会无限期地保持锁定状态
缓解措施: 避免在显式退出调用期间重新质押,或者清楚地记录该行为
88. 代理构造函数初始化中缺少验证
模式: 代理构造函数接受任意 calldata,而不验证函数选择器或参数。
有漏洞的代码示例 (Dolomite):
constructor(
address _berachainRewardsRegistry,
bytes memory _initializationCalldata
) {
// 没有验证 calldata 内容
Address.functionDelegateCall(
implementation(),
_initializationCalldata,
"Initialization failed"
);
}
影响:
- 代理可以使用无效参数进行初始化
- 零地址可以设置为关键组件
- 部署期间可以调用意外的函数
缓解措施:
// 验证函数选择器
require(bytes4(_initializationCalldata[0:4]) == bytes4(keccak256("initialize(address)")));
// 验证参数
address vaultFactory = abi.decode(_initializationCalldata[4:], (address));
require(vaultFactory != address(0), "Zero vault factory");
89. vault 类型转换期间的奖励损失
模式: 更改奖励 vault 类型,而不从先前的类型中正确声明奖励。
有漏洞的代码示例 (Dolomite):
function _setDefaultRewardVaultTypeByAsset(address _asset, RewardVaultType _type) internal {
RewardVaultType currentType = getDefaultRewardVaultTypeByAsset(_asset);
if (currentType != _type) {
_getReward(_asset); // 始终尝试从 INFRARED 类型获取
REGISTRY().setDefaultRewardVaultTypeFromMetaVaultByAsset(_asset, _type);
}
}
function _getReward(address _asset) internal {
// 硬编码为 INFRARED,忽略用户的当前类型
RewardVaultType rewardVaultType = RewardVaultType.INFRARED;
IInfraredVault rewardVault = IInfraredVault(REGISTRY().rewardVault(_asset, rewardVaultType));
}
影响: 在 vault 类型之间转换时永久损失奖励
90. 协议暂停期间奖励不可访问
模式: 当质押暂停时,奖励的自动再投资失败,并且没有其他赎回路径。
有漏洞的代码示例 (Dolomite):
function _handleRewards(UserReward[] memory _rewards) internal {
if (_rewards[i].token == UNDERLYING_TOKEN()) {
// 尝试重新质押,如果暂停将失败
factory.depositIntoDolomiteMargin(DEFAULT_ACCOUNT_NUMBER, _rewards[i].amount);
}
}
// 外部 vault 具有可暂停的质押
function stake(uint256 amount) external whenNotPaused {
// 暂停时会 revert
}
影响: 用户在暂停期间无法访问获得的奖励
缓解措施: 在重新投资之前检查暂停状态,提供替代路径
91. 代理模式中不一致的 ETH 处理
模式: 不同的代理合约通过 receive() 和 fallback() 函数以不同的方式处理 ETH。
示例:
// 一些代理委托两者
receive() external payable { _callImplementation(implementation()); }
fallback() external payable { _callImplementation(implementation()); }
// 其他代理仅委托 fallback
receive() external payable {} // 空
fallback() external payable { _callImplementation(implementation()); }
影响: 混乱和潜在的 ETH 处理问题
92. 通过恶意 Hook 绕过重入保护(Bunni v2)
模式: 配置了恶意 Hook 的池可以通过直接调用解锁函数来绕过主合约的重入保护。
有漏洞的代码示例 (Bunni v2):
function lockForRebalance(PoolKey calldata key) external notPaused(6) {
if (msg.sender != address(key.hooks)) revert BunniHub__Unauthorized();
_nonReentrantBefore();
}
function unlockForRebalance(PoolKey calldata key) external notPaused(7) {
if (msg.sender != address(key.hooks)) revert BunniHub__Unauthorized();
_nonReentrantAfter();
}
攻击场景:
- 部署恶意 Hook 合约
- Hook 调用
unlockForRebalance()
以禁用重入保护 - 对
hookHandleSwap()
的递归调用会耗尽原始余额和 vault 储备金 - 设置 state 而不是递减,避免下溢
影响: 完全耗尽所有合法池的资金(在披露时为 733 万美元)
缓解措施:
- 实施每个池的重入保护
- 将 Hook 限制为规范实现
- 添加 Hook 白名单
93. Token 转移中的跨合约重入(Bunni v2)
模式: 在解锁器回调之前执行的 Token 转移 Hook 可能会启用跨合约重入。
有漏洞的代码示例:
function transfer(address to, uint256 amount) public virtual override returns (bool) {
...
_afterTokenTransfer(msgSender, to, amount);
// 如果 `to` 被锁定,则进行解锁器回调。
if (toLocked) {
IERC20Unlocker unlocker = unlockerOf(to);
unlocker.lockedUserReceiveCallback(to, amount);
}
return true;
}
影响:
- Hooklet 在中间状态上执行
- 锁定的接收者可以在转移期间操纵状态
- 推荐奖励会计损坏
缓解措施: 在所有回调完成后执行 Hook
94. 原始余额更新中的 vault 费用边缘情况
模式: 当 vault 在存款/取款期间未提取预期的资产金额时,会计不正确。
有漏洞的代码示例 (Bunni v2):
function _updateVaultReserveViaClaimTokens(int256 rawBalanceChange, Currency currency, ERC4626 vault)
internal
returns (int256 reserveChange, int256 actualRawBalanceChange)
{
...
reserveChange = vault.deposit(absAmount, address(this)).toInt256();
// 在此处使用 absAmount 是安全的,因为在最坏的情况下,vault.deposit() 调用提取了比请求的 token 更少的 token
actualRawBalanceChange = -absAmount.toInt256(); // 有漏洞:假设已存入全部金额
...
}
影响:
- 不正确的原始余额会计
- 流动性提供者的少量损失
- 潜在的再平衡订单通货膨胀
缓解措施: 使用实际余额变化而不是假设金额
95. vault 小数位数处理不足
模式: 假设 vault 份额 token 小数位数与底层资产小数位数匹配。
有漏洞的代码示例 (Bunni v2):
// 计算当前份额价格
uint120 sharePrice0 =
bunniState.reserve0 == 0 ? 0 : reserveBalance0.divWadUp(bunniState.reserve0).toUint120();
uint120 sharePrice1 =
bunniState.reserve1 == 0 ? 0 : reserveBalance1.divWadUp(bunniState.reserve1).toUint120();
真实示例: 用于 8 位小数 WBTC 的具有 18 位小数份额的 Morpho vault
影响:
- 触发错误的激增
- 份额价格溢出会导致 DoS
- 不正确的费用计算
缓解措施: 根据实际小数位数显式缩放值
96. Oracle Tick 验证漏洞
模式: 各种 LDF 函数中缺少对 Oracle 派生的 Tick 的验证。
有漏洞的代码示例 (Bunni v2):
function floorPriceToRick(uint256 floorPriceWad, int24 tickSpacing) public view returns (int24 rick) {
// 没有验证 rick 是否在可用范围内
uint160 sqrtPriceX96 = ((floorPriceWad << 192) / WAD).sqrt().toUint160();
rick = sqrtPriceX96.getTickAtSqrtPrice();
rick = bondLtStablecoin ? rick : -rick;
rick = roundTickSingle(rick, tickSpacing);
}
影响: 操作可能在可用 Tick 范围之外执行
97. 动态 LDF 转换模式 DoS
模式: 强制转换模式而不验证结果 Tick 范围可能会导致 DoS。
有漏洞的代码示例 (Bunni v2):
if (initialized) {
int24 tickLength = tickUpper - tickLower;
tickLower = enforceShiftMode(tickLower, lastTickLower, shiftMode);
tickUpper = tickLower + tickLength; // 可能会超过 maxUsableTick!
shouldSurge = tickLower != lastTickLower;
}
影响: 需要 LDF 查询的池操作的完全 DoS
98. 使用 am-AMM 进行费用计算溢出
模式: 当 am-AMM 激增费用处于活动状态时,总费用超过 100%。
有漏洞的代码示例 (Bunni v2):
// 激增费用可以是 100%
swapFee = useAmAmmFee
? uint24(FixedPointMathLib.max(amAmmSwapFee, computeSurgeFee(lastSurgeTimestamp, hookParams.surgeFeeHalfLife)))
: hookFeesBaseSwapFee;
// 在顶部添加 Hook 费用
if(useAmAmmFee) {
hookFeesAmount = outputAmount.mulDivUp(hookFeesBaseSwapFee, SWAP_FEE_BASE).mulDivUp(
env.hookFeeModifier, MODIFIER_BASE
);
}
// 总计可能会超过 outputAmount!
outputAmount -= swapFeeAmount + hookFeesAmount;
影响: 交换期间的意外 revert
99. 排队的取款时间戳溢出
模式: 对取款时间戳进行未检查的算术运算会导致边缘情况下的故障。
有漏洞的代码示例 (Bunni v2):
// 使用未检查的算法,如果发生溢出,则使 unlockTimestamp 溢出回 0
uint56 newUnlockTimestamp;
unchecked {
newUnlockTimestamp = uint56(block.timestamp) + WITHDRAW_DELAY;
}
// 但是稍后:
if (queued.unlockTimestamp + WITHDRAW_GRACE_PERIOD >= block.timestamp) { // 可能会溢出!
revert BunniHub__NoExpiredWithdrawal();
}
影响: 接近 uint56 max 的排队取款的永久 DoS
100. 即时 (JIT) 流动性膨胀
模式: 在触发再平衡的交换之前添加的 JIT 流动性可能会膨胀订单金额。
攻击场景 (Bunni v2):
- 在交换之前添加#### 102. 窄类型允许量计算中的高位 模式:地址中潜在的脏高位会影响 keccak256 哈希计算。
有漏洞的代码示例 (Bunni v2):
function approve(address spender, uint256 amount) public virtual override returns (bool) {
assembly {
// 计算 allowance 的槽位并存储 amount。
mstore(0x20, spender) // 高位可能不干净
mstore(0x0c, _ALLOWANCE_SLOT_SEED)
mstore(0x00, msgSender)
sstore(keccak256(0x0c, 0x34), amount)
}
}
影响:错误的 allowance 槽位计算
103. 缺少最低份额滑点保护
模式:存款只验证 token 数量,不验证收到的份额。
有漏洞的代码示例 (Bunni v2):
// 检查滑点
if (amount0 < params.amount0Min || amount1 < params.amount1Min) {
revert BunniHub__SlippageTooHigh();
}
// 但是没有检查收到的份额!
使用恶意 Vault 的攻击:
- Vault 在受害者存款前抬高其份额价格
- 尽管通过了滑点检查,受害者收到的份额却很少
- 攻击者从抬高的份额价值中获利
缓解措施:向存款参数添加 sharesMin
参数
104. 缺少 CCIP 源验证 (YieldFi)
模式:跨链消息处理程序未验证发送者,允许任意消息注入。
有漏洞的代码示例 (YieldFi):
function _ccipReceive(Client.Any2EVMMessage memory any2EvmMessage) internal override {
bytes memory message = abi.decode(any2EvmMessage.data, (bytes));
BridgeSendPayload memory payload = Codec.decodeBridgeSendPayload(message);
// 没有验证消息发送者!
if (isL1) {
ILockBox(lockboxes[payload.token]).unlock(payload.token, payload.to, payload.amount);
} else {
// 在 L2 上铸造 token
IManager(manager).manageAssetAndShares(payload.to, manageAssetAndShares);
}
}
影响:攻击者可以耗尽 L1 上的 lockbox 或在 L2 上铸造无限量的 token
缓解措施:实施可信对等方验证:
mapping(uint64 => mapping(address => bool)) public allowedPeers;
address sender = abi.decode(any2EvmMessage.sender, (address));
require(allowedPeers[any2EvmMessage.sourceChainSelector][sender], "!allowed");
105. CCIP 链 ID 类型不匹配 (YieldFi)
模式:为跨链标识符使用不正确的数据类型导致消息解码失败。
有漏洞的代码示例 (YieldFi):
// Codec 期望链 ID 为 uint32
(uint32 dstId, address to, address token, uint256 amount, bytes32 trxnType) =
abi.decode(_data, (uint32, address, address, uint256, bytes32));
// 但是 CCIP 使用 uint64 链选择器(例如,Ethereum:5009297550715157269)
影响:所有 CCIP 消息在解码期间都会 revert,导致永久性的资金损失
缓解措施:将链 ID 类型更新为 uint64
106. 委托提款中错误的 Owner (YieldFi)
模式:在委托提款场景中,将不正确的 owner 地址传递给下游函数。
有漏洞的代码示例 (YieldFi):
function _withdraw(address caller, address receiver, address owner, uint256 assets, uint256 shares)
internal override {
if (caller != owner) {
_spendAllowance(owner, caller, shares);
}
// 有漏洞:传递 msg.sender 而不是 owner
IManager(manager).redeem(msg.sender, address(this), asset(), shares, receiver, address(0), "");
}
影响:委托提款失败或烧毁错误用户的 token
缓解措施:传递正确的 owner
参数
107. 不正确的 ERC4626 舍入方向 (YieldFi)
模式:preview 函数的舍入有利于用户而不是 Vault。
有漏洞的代码示例 (YieldFi L2):
// previewMint - 应该向上舍入(不利于用户)
function previewMint(uint256 shares) public view override returns (uint256) {
return (grossShares * exchangeRate()) / Constants.PINT; // 向下舍入!
}
// previewWithdraw - 应该向上舍入(不利于用户)
function previewWithdraw(uint256 assets) public view override returns (uint256) {
uint256 sharesWithoutFee = (assets * Constants.PINT) / exchangeRate(); // 向下舍入!
}
影响:通过舍入误差从 Vault 缓慢泄漏价值
缓解措施:根据 EIP-4626 实施正确的舍入方向
108. 缺少 L2 Sequencer 正常运行时间检查 (YieldFi)
模式:在读取 oracle 数据时,未验证 L2 sequencer 状态。
有漏洞的代码示例 (YieldFi):
function fetchExchangeRate(address token) external view returns (uint256) {
(, int256 answer,, uint256 updatedAt,) = IOracle(oracle).latestRoundData();
require(answer > 0, "Invalid price");
require(block.timestamp - updatedAt < staleThreshold, "Stale price");
// 缺少 sequencer 正常运行时间检查!
}
影响:在 sequencer 停机期间,陈旧的价格显得新鲜
缓解措施:为 L2 部署添加 sequencer 正常运行时间验证
109. 低于提款阈值的直接存款 (YieldFi)
模式:允许导致份额低于最低提款额的存款。
有漏洞的代码示例 (YieldFi):
// YToken 存款没有最小检查
function _deposit(...) internal {
require(receiver != address(0) && assets > 0 && shares > 0, "!valid");
// 没有针对 minSharesInYToken 的检查!
}
// 但是 Manager 提款强制执行最小值
function _validate(...) internal {
require(_amount >= minSharesInYToken[_yToken], "!minShares");
}
影响:用户可以存入可能被永久锁定的金额
缓解措施:在 YToken 存款中强制执行最小份额
110. 无效的费用份额计算 (YieldFi)
模式:当费用为零时,返回错误的值导致下溢。
有漏洞的代码示例 (YieldFi):
function _transferFee(address _yToken, uint256 _shares, uint256 _fee) internal returns (uint256) {
if (_fee == 0) {
return _shares; // 错误:应该返回 0
}
uint256 feeShares = (_shares * _fee) / Constants.HUNDRED_PERCENT;
IERC20(_yToken).safeTransfer(treasury, feeShares);
return feeShares;
}
// 稍后会导致下溢:
uint256 sharesAfterAllFee = adjustedShares - adjustedFeeShares - adjustedGasFeeShares;
影响:零费用的存款 revert
缓解措施:当费用为 0 时返回 0
111. Yield 分配中不正确的赎回会计 (Critical)
模式:在 yield 阶段处理赎回时,错误地将 yield 包含在基础资产计算中,该计算会从跟踪的存款中扣除。
有漏洞的代码示例 (Strata):
function _withdraw(address caller, address receiver, address owner, uint256 assets, uint256 shares) internal override {
if (PreDepositPhase.YieldPhase == currentPhase) {
assets += previewYield(caller, shares); // 添加 yield
uint sUSDeAssets = sUSDe.previewWithdraw(assets);
_withdraw(
address(sUSDe),
caller,
receiver,
owner,
assets, // 这包括 yield,但会从 depositedBase 中扣除!
sUSDeAssets,
shares
);
}
}
// 在 MetaVault 中
function _withdraw(..., uint256 baseAssets, ...) internal {
depositedBase -= baseAssets; // 当 baseAssets 包括 yield 时会下溢
}
影响:
- 存款基础跟踪遭到破坏
- 后续的 yield 计算会放大错误
- 攻击者可以耗尽整个协议余额
缓解措施:
uint256 assetsPlusYield = assets + previewYield(caller, shares);
uint sUSDeAssets = sUSDe.previewWithdraw(assetsPlusYield);
_withdraw(
address(sUSDe),
caller,
receiver,
owner,
assets, // 仅基本资产,不包括 yield
sUSDeAssets,
shares
);
112. 阶段转换期间的多 Vault 提款失败 (High)
模式:由于不正确的提款逻辑,在 yield 阶段无法访问支持的 Vault 资产。
有漏洞的代码示例 (Strata):
function _withdraw(address caller, address receiver, address owner, uint256 assets, uint256 shares) internal override {
if (PreDepositPhase.YieldPhase == currentPhase) {
// 仅处理 sUSDe 提款
uint sUSDeAssets = sUSDe.previewWithdraw(assets);
_withdraw(address(sUSDe), ...);
return;
}
// Points 阶段逻辑
uint USDeBalance = USDe.balanceOf(address(this));
if (assets > USDeBalance) {
redeemRequiredBaseAssets(assets - USDeBalance);
}
}
影响:通过支持的 Vault 存款的用户无法在 yield 阶段提取其应得的资产。
缓解措施:添加逻辑以处理 yield 阶段期间支持的 Vault 提款,或在 yield 阶段阻止添加 Vault。
113. 不完整的多 Vault 赎回逻辑 (Medium)
模式:如果单个 Vault 可以满足整个请求的金额,redeemRequiredBaseAssets
仅从该 Vault 提款。
有漏洞的代码示例 (Strata):
function redeemRequiredBaseAssets(uint baseTokens) internal {
for (uint i = 0; i < assetsArr.length; i++) {
IERC4626 vault = IERC4626(assetsArr[i].asset);
uint totalBaseTokens = vault.previewRedeem(vault.balanceOf(address(this)));
if (totalBaseTokens >= baseTokens) { // 仅在单个 Vault 足够时才提款
vault.withdraw(baseTokens, address(this), address(this));
break;
}
}
}
影响:
- 即使多个 Vault 中存在足够的资产,提款也会失败
- 提取的超额资产仍然未抵押
缓解措施:实施从多个 Vault 提款并跟踪剩余所需金额的逻辑。
114. 具有未检查 Revert 的 Preview 函数违规 (Medium)
模式:使用 previewRedeem
而不是 maxWithdraw
进行可用性检查,当 Vault 暂停或有限制时会导致 DoS。
有漏洞的代码示例 (Strata):
function redeemRequiredBaseAssets(uint baseTokens) internal {
for (uint i = 0; i < assetsArr.length; i++) {
IERC4626 vault = IERC4626(assetsArr[i].asset);
// previewRedeem 不考虑暂停状态或限制
uint totalBaseTokens = vault.previewRedeem(vault.balanceOf(address(this)));
if (totalBaseTokens >= baseTokens) {
vault.withdraw(baseTokens, address(this), address(this));
}
}
}
EIP-4626 规范:previewRedeem
绝不能考虑赎回限制,并且应表现得好像赎回将被接受。
缓解措施:使用 maxWithdraw()
检查可用性
115. 通过舍入方向错误泄漏价值 (Medium)
模式:使用 previewWithdraw
(向上舍入) 来计算转出的金额,导致价值泄漏。
有漏洞的代码示例 (Strata):
function _withdraw(...) internal override {
if (PreDepositPhase.YieldPhase == currentPhase) {
assets += previewYield(caller, shares);
// previewWithdraw 向上舍入(不利于协议)
uint sUSDeAssets = sUSDe.previewWithdraw(assets);
// 将这个向上舍入的金额转出
SafeERC20.safeTransfer(IERC20(token), receiver, sUSDeAssets);
}
}
影响:每次赎回都会以牺牲剩余存款人的利益为代价,使赎回人受益,造成价值泄漏。
缓解措施:在计算转移金额时,使用向下舍入的 convertToShares
。
116. 通过 yield 阶段的捐赠操纵份额价格 (Critical)
模式:totalAssets()
未考虑直接 token 转移,从而启用份额价格操纵。
有漏洞的代码示例 (Strata):
function totalAssets() public view override returns (uint256) {
return depositedBase; // 不考虑实际的 sUSDe 余额
}
function previewYield(address caller, uint256 shares) public view returns (uint256) {
uint total_sUSDe = sUSDe.balanceOf(address(this)); // 看到捐赠的余额
uint total_USDe = sUSDe.previewRedeem(total_sUSDe);
uint total_yield_USDe = total_USDe - Math.min(total_USDe, depositedBase);
// 由于捐赠导致的 inflate yield
}
攻击场景:
- 当 Vault 为空时存入最少的金额
- 直接捐赠大量 sUSDe
- Yield 计算看到 inflate 的余额
- 新的存款人获得更少的份额
缓解措施:在 yield 阶段将实际 token 余额包含在 totalAssets()
中。
117. 多 Vault 中的最低份额保护绕过 (High)
模式:绕过最低份额检查的替代提款路径。
有漏洞的代码示例 (Strata):
// MetaVault 提款不检查非基础资产的最低份额
function _withdraw(address token, ...) internal virtual {
SafeERC20.safeTransfer(IERC20(token), receiver, tokenAssets);
onAfterWithdrawalChecks(); // 仅在提取基础资产时检查
}
function onAfterWithdrawalChecks() internal view {
if (totalSupply() < MIN_SHARES) {
revert MinSharesViolation();
}
}
影响:可以通过 meta Vault 路径绕过首位存款人攻击保护。
118. 状态更新中的跨函数重入 (Medium)
模式:状态更新在多个操作中拆分,没有重入保护。
有漏洞的代码示例 (Strata):
function _deposit(address token, ...) internal virtual {
depositedBase += baseAssets; // 状态更新 1
SafeERC20.safeTransferFrom(IERC20(token), caller, address(this), tokenAssets); // 外部调用
_mint(receiver, shares); // 状态更新 2
}
影响:ERC777 token 或带有Hook的 token 可以在状态更新之间重入。
119. 硬编码滑点参数 (Medium)
模式:无法适应市场情况的固定滑点容差。
有漏洞的代码示例 (Strata):
uint256 amountOutMin = (amount * 999) / 1000; // 仅 0.1% 的滑点保护
影响:在高波动期间的 DoS 或正常情况下的价值损失。
120. 多级调用中不正确的功能路由 (Low)
模式:在 Vault 层次结构中调用错误的功能变体。
有漏洞的代码示例 (Strata):
function redeem(address token, uint256 shares, address receiver, address owner) public returns (uint256) {
if (token == asset()) {
return withdraw(shares, receiver, owner); // 应该调用 redeem,而不是 withdraw
}
}
121. 重复的 Vault 数组条目 (Low)
模式:允许跟踪数组中存在重复条目,导致迭代问题。
有漏洞的代码示例 (Strata):
function addVaultInner(address vaultAddress) internal {
TAsset memory vault = TAsset(vaultAddress, EAssetType.ERC4626);
assetsMap[vaultAddress] = vault;
assetsArr.push(vault); // 没有重复检查
}
影响:Gas 浪费、潜在的 DoS 和删除失败。
122. 多 Vault 系统中缺少资产验证 (Low)
模式:未验证添加的 Vault 是否共享相同的底层资产。
有漏洞的代码示例 (Strata):
function addVaultInner(address vaultAddress) internal {
// 没有检查 IERC4626(vaultAddress).asset() == asset()
TAsset memory vault = TAsset(vaultAddress, EAssetType.ERC4626);
assetsMap[vaultAddress] = vault;
}
影响:如果添加了具有不同资产的 Vault,则会计腐败。
123. 阶段转换状态不一致 (Low)
模式:在没有明确理由的情况下,在阶段转换期间删除 Vault 支持。
有漏洞的代码示例 (Strata):
function startYieldPhase() external onlyOwner {
setYieldPhaseInner();
redeemMetaVaults(); // 还会删除所有 Vault 支持
// 但是 Vault 可以在之后立即重新添加
}
124. View 函数中的 EIP-4626 合规性违规 (Low)
模式:Max 函数未按照 EIP-4626 的要求考虑暂停状态。
有漏洞的代码示例 (Strata):
// 当按照 EIP-4626 的要求禁用提款时,不返回 0
function maxWithdraw(address owner) public view override returns (uint256) {
return previewRedeem(balanceOf(owner));
// 应该检查:if (!withdrawalsEnabled) return 0;
}
影响:与期望 EIP-4626 合规性的协议集成失败。
125. 可升级合约中的存储布局风险 (Low)
模式:没有适当存储布局保护的可升级合约。
有漏洞的代码示例 (Strata):
abstract contract MetaVault is IMetaVault, PreDepositVault {
uint256 public depositedBase;
TAsset[] public assetsArr;
mapping(address => TAsset) public assetsMap;
// 没有存储间隙或 ERC7201 命名空间
}
缓解措施:使用 ERC7201 命名空间的存储或存储间隙。
126. ERC4626 Vault 费用绕过 (Critical)
模式:协议没有从用户那里转移足够的 token 来支付 ERC4626 Vault 存取款费用。
有漏洞的代码示例 (Burve):
function addValue(...) external returns (uint256[MAX_TOKENS] memory requiredBalances) {
// ...
uint256 realNeeded = AdjustorLib.toReal(token, requiredNominal[i], true);
requiredBalances[i] = realNeeded;
TransferHelper.safeTransferFrom(token, msg.sender, address(this), realNeeded);
Store.vertex(VertexLib.newId(i)).deposit(cid, realNeeded); // 此处收取费用!
}
影响:
- 用户可以避免支付 Vault 费用
- 协议抵押不足
- 最后的用户在提款时遭受损失
缓解措施:计算并转移额外的 token 以支付 Vault 费用。
127. Adjustor 实现反转 (High)
模式:toNominal
和 toReal
函数在 ERC4626ViewAdjustor 中被往后实现。
有漏洞的代码示例 (Burve):
function toNominal(address token, uint256 real, bool) external view returns (uint256 nominal) {
IERC4626 vault = getVault(token);
return vault.convertToShares(real); // 错误:应该使用 convertToAssets
}
function toReal(address token, uint256 nominal, bool) external view returns (uint256 real) {
IERC4626 vault = getVault(token);
return vault.convertToAssets(nominal); // 错误:应该使用 convertToShares
}
影响:用户存入的 LST token 多于所需数量,导致重大损失。
缓解措施:反转两个函数的实现。
128. Vault 提款中的净额计算错误 (High)
模式:当存取款都处于挂起状态时,不正确的净额计算。
有漏洞的代码示例 (Burve):
if (assetsToWithdraw > assetsToDeposit) {
assetsToDeposit = 0;
assetsToWithdraw -= assetsToDeposit; // 错误:减去 0!
}
影响:
- 提款费用按全额支付,而不是净额
- 协议效率损失
- 不必要的 gas 成本
缓解措施:先减去,再设置为零。
129. 单边费用分配稀释 (High)
模式:税务分配在得到奖励之前,将新的 LP 包含在分母中。
有漏洞的代码示例 (Burve):
function addValueSingle(...) internal {
self.valueStaked += value; // 更新状态
self.bgtValueStaked += bgtValue;
// 税务计算...
}
// 稍后在 addEarnings 中:
self.earningsPerValueX128[idx] +=
(reserveShares << 128) /
(self.valueStaked - self.bgtValueStaked); // 包括新的抵押者!
影响:现有 LP 收到 diluted 的费用份额;新的 LP 不公平地收到他们自己的税的一部分。
缓解措施:在更新 valueStaked 之前分配税款。
130. 区间重新进入费用捕获攻击 (High)
模式:攻击者在区间回到范围内时,通过定时存款来捕获累积的费用。
攻击场景 (Burve):
- Burve 区间超出 Uniswap V3 区间
- 费用在不需要的 token 中累积
- 攻击者在区间重新进入之前进行存款
- 触发费用复合,捕获不成比例的份额
- 立即提取利润
影响:费用狙击攻击从合法的 LP 那里窃取累积的奖励。
缓解措施:添加一个小的始终在范围内的仓位,以确保连续的费用复合。
131. removeValueSingle 中的费用绕过 (High)
模式:由于读取未初始化的变量,因此零 realTax
计算。
有漏洞的代码示例 (Burve):
function removeValueSingle(...) returns (uint256 removedBalance) {
// ...
uint256 realTax = FullMath.mulDiv(
removedBalance, // 此处仍然为 0!
nominalTax,
removedNominal
);
}
影响:完全绕过单 token 移除的费用。
缓解措施:使用 realRemoved
而不是 removedBalance
。
132. NoopVault 捐赠攻击 (High)
模式:不受保护的 ERC4626 实现容易受到经典的捐赠攻击。
攻击路径 (Burve):
- 攻击者用 1 wei 前置首笔存款
- 直接向 Vault 捐赠大量金额
- 由于舍入,合法用户收到 0 份额
- 攻击者提取 inflate 的金额
影响:完全耗尽用户存款。
缓解措施:实现虚拟份额或初始存款保护。
133. removeValueSingle 中的双倍提款 (High)
模式:税款未包含在 Vault 提款金额中,导致余额不足。
有漏洞的代码示例 (Burve):
Store.vertex(vid).withdraw(cid, realRemoved, false); // 不包括税款
// ...
c.addEarnings(vid, realTax); // 需要税款金额
removedBalance = realRemoved - realTax; // 提取的金额不足!
影响:由于余额不足,该函数 revert。
缓解措施:从 Vault 中提取 realRemoved + realTax
。
134. 储备份额溢出攻击 (Medium)
模式:重复的小幅修剪会导致份额呈指数级上涨。
有漏洞的代码示例 (Burve):
shares = (balance == 0)
? amount * SHARE_RESOLUTION
: (amount * reserve.shares[idx]) / balance; // 当余额 ≈ 0 时爆炸
影响:
- 份额计数器溢出
- 完全协议 DoS
- 不可逆转的状态损坏
缓解措施:强制执行修剪的最低余额阈值。
135. Admin 参数更改抢先交易 (Medium)
模式:用户可以利用效率因子变化而无需重新平衡。
攻击场景 (Burve):
- Admin 提高效率因子
e
- 攻击者通过
removeTokenForValue
进行反向交易 - 精心设计的金额,使
newTargetX128
等于原始金额 - 提取用于储备的超额 token
影响:盗窃再平衡利润。
缓解措施:更改效率因子时强制重新平衡。
136. Diamond 中缺少 acceptOwnership (Medium)
模式:未添加所有权接受的功能选择器。
有漏洞的代码示例 (Burve):
adminSelectors[0] = BaseAdminFacet.transferOwnership.selector;
adminSelectors[1] = BaseAdminFacet.owner.selector;
adminSelectors[2] = BaseAdminFacet.adminRights.selector;
// 缺少:acceptOwnership.selector
影响:无法完成所有权转移。
缓解措施:将 acceptOwnership
选择器添加到 Admin 面。
137. Vault 暂停期间的协议费用损失 (Medium)
模式:当 Vault 禁用提款时,协议费用错误地发送给用户。
攻击场景 (Burve):
- 协议费用在 Diamond 中累积
- Vault 暂时禁用提款
- 用户收集收益
- 提款净额结算失败
- 协议费用转移给用户
影响:协议收入损失。
缓解措施:如果 Vault 禁用提款,则 Revert。
138. 跨 ValueToken 套利 (Medium)
模式:尽管底层值不同,但所有 Closure 中都使用了相同的 ValueToken。
攻击路径 (Burve):
- 向低价值 Closure 添加流动性
- 铸造 ValueToken
- 在高价值 Closure 中烧毁 ValueToken
- 提取更有价值的 token
影响:从合法的 LP 中提取价值。
缓解措施:每个 Closure 使用单独的 ValueToken。
139. 通过无限制增长打破不变量 (Medium)
模式:目标值可以增长超出设计的 deMinimus 范围。
有漏洞的代码示例 (Burve):
self.targetX128 += valueX128 / self.n + ((valueX128 % self.n) > 0 ? 1 : 0);
// 没有检查:|value - target*n| <= deMinimus*n
影响:协议不变量违反,影响交换定价。
缓解措施:在添加后使用 ValueLib.t
重新计算目标。
140. removeValueSingle 排序时收益损失 (Medium)
模式:在 trimBalance
之前删除资产会使用过时的收益。
有漏洞的代码示例 (Burve):
Store.assets().remove(recipient, cid, value, bgtValue); // 使用旧收益
// ...
(uint256 removedNominal, uint256 nominalTax) = c.removeValueSingle(...);
// 更新收益后!
影响:用户在非活动池中损失 >1% 的收益。
缓解措施:在资产移除之前调用 trimBalance
。
141. ERC4626 策略返回值混淆 (BakerFi)
模式:策略函数返回份额而不是资产,导致会计错误。
有漏洞的代码示例 (BakerFi):
// StrategySupplyERC4626
function _deploy(uint256 amount) internal override returns (uint256) {
return _vault.deposit(amount, address(this)); // 返回份额,而不是资产!
}
function _undeploy(uint256 amount) internal override returns (uint256) {
return _vault.withdraw(amount, address(this), address(this)); // 返回份额!
}
function _getBalance() internal view override returns (uint256) {
return _vault.balanceOf(address(this)); // 返回份额,而不是资产!
}
影响:
- Vault 中不正确的份额计算
- 用户无法提取全部存款
- 永久资金锁定
缓解措施:
function _deploy(uint256 amount) internal override returns (uint256) {
_vault.deposit(amount, address(this));
return amount; // 返回已部署的资产
}
function _getBalance() internal view override returns (uint256) {
return _vault.convertToAssets(_vault.balanceOf(address(this)));
}
142. 策略收获时缺少访问控制 (BakerFi)
模式:任何人都可以调用 harvest 来操纵绩效费用计算。
有漏洞的代码示例 (BakerFi):
function harvest() external returns (int256 balanceChange) { // 没有访问控制!
uint256 newBalance = getBalance();
balanceChange = int256(newBalance) - int256(_deployedAmount);
_deployedAmount = newBalance; // 更新状态
}
影响:用户可以抢先交易 Rebalance 调用以避免绩效费用。
缓解措施:将 onlyOwner
修饰符添加到 harvest 函数。
143. 在 Undeploy 上未更新已部署的金额 (BakerFi)
模式:从策略中提取资产时,策略的 _deployedAmount
不会减少。
有漏洞的代码示例 (BakerFi):
function undeploy(uint256 amount) external returns (uint256) {
uint256 withdrawalValue = _undeploy(amount);
ERC20(_asset).safeTransfer(msg.sender, withdrawalValue);
balance -= amount;
// _deployedAmount 未更新!
return amount;
}
影响:
- 不正确的收获计算显示损失
- 即使有利润也无法收取绩效费用
缓解措施:在 undeploy 函数中更新 _deployedAmount
。
144. 多策略十进制处理问题 (BakerFi)
模式:Vault(18 个十进制)和策略之间的十进制处理不一致。
有漏洞的代码示例 (BakerFi):
// Vault 假设份额为 18 个十进制
function _depositInternal(uint256 assets, address receiver) returns (uint256 shares) {
uint256 deployedAmount = _deploy(assets); // 可能返回不同的十进制
shares = total.toBase(deployedAmount, false);
}
// 策略返回本地 token 十进制
function _deploy(uint256 amount) internal returns (uint256) {
// 返回 token 的本地十进制金额(例如,USDC 为 6)
}
影响:
- 份额计算错误
- 提款失败
- 系统与非 18 个十进制 token 不兼容
缓解措施:将所有金额标准化为 18 个十进制,或使 Vault 十进制与底层 token 对齐。
145. Vault Router 许可漏洞 (BakerFi)
模式:可以抢先交易许可签名以窃取用户 token。
有漏洞的代码示例 (BakerFi):
function pullTokensWithPermit(
IERC20Permit token,
uint256 amount,
address owner,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal virtual {
IERC20Permit(token).permit(owner, address(this), amount, deadline, v, r, s);
IERC20(address(token)).safeTransferFrom(owner, address(this), amount);
}
攻击场景:
- 用户提交带有许可签名的交易
- 攻击者在 mempool 中看到它,提取签名
- 攻击者使用用户的签名调用 Router
- 然后,攻击者调用
sweepTokens
以窃取资金
影响:完全窃取用户资金。
缓解措施:从 Router 中删除许可功能或实现 Nonce 跟踪。
146. Vault Router 允许量利用 (BakerFi)
模式:任何人都可以通过 Router 命令耗尽批准的 token。
有漏洞的代码示例 (BakerFi):
// 命令允许任意 token 移动
function pullTokenFrom(IERC20 token, address from, uint256 amount) internal {
if (token.allowance(from, address(this)) < amount) revert NotEnoughAllowance();
IERC20(token**缓解措施**:根据 ERC4626 规范实现 view 函数。
#### 149. 多重策略新增策略 DoS (BakerFi)
**模式**:未经批准添加新策略会导致存款/取款失败。
**存在漏洞的代码示例** (BakerFi):
```solidity
function addStrategy(IStrategy strategy) external onlyRole(VAULT_MANAGER_ROLE) {
_strategies.push(strategy);
_weights.push(0);
// 没有给策略任何批准!
}
影响:当尝试部署到未经批准的策略时,金库操作失败。
缓解措施:添加策略时,批准策略并设置最大额度。
150. 杠杆策略移除会计错误 (BakerFi)
模式:由于收到的金额假设不正确,移除杠杆策略失败。
存在漏洞的代码示例 (BakerFi):
function removeStrategy(uint256 index) external {
uint256 strategyAssets = _strategies[index].totalAssets();
if (strategyAssets > 0) {
IStrategy(_strategies[index]).undeploy(strategyAssets);
_allocateAssets(strategyAssets); // 假设收到全部金额!
}
}
影响:由于杠杆策略返回的金额少于请求的金额,交易会回滚。
缓解措施:使用实际返回的金额进行分配。
151. 金库因直接策略转移而不可用 (BakerFi)
模式:直接将 token 转移到策略会导致金库永久不可用。
存在漏洞的代码示例 (BakerFi):
function _depositInternal(uint256 assets, address receiver) returns (uint256 shares) {
Rebase memory total = Rebase(totalAssets(), totalSupply());
// 如果 totalAssets > 0 但 totalSupply == 0,则回滚
if (!((total.elastic == 0 && total.base == 0) || (total.base > 0 && total.elastic > 0))) {
revert InvalidAssetsState();
}
}
攻击:在首次存款之前,直接将 token 发送到策略。
影响:对金库的永久 DoS。
缓解措施:为极端情况添加恢复机制。
152. 通过接收者绕过存款限制 (BakerFi)
模式:最大存款限制仅检查 msg.sender,不检查接收者。
存在漏洞的代码示例 (BakerFi):
function _depositInternal(uint256 assets, address receiver) returns (uint256 shares) {
uint256 maxDepositLocal = getMaxDeposit();
if (maxDepositLocal > 0) {
uint256 depositInAssets = (balanceOf(msg.sender) * _ONE) / tokenPerAsset();
// 仅检查 msg.sender,不检查 receiver!
if (newBalance > maxDepositLocal) revert MaxDepositReached();
}
_mint(receiver, shares);
}
影响:通过使用不同的接收者地址进行无限制的存款。
缓解措施:在映射中按实际存款人跟踪存款。
153. 金库未暂停时重新平衡 (BakerFi)
模式:当金库暂停时,重新平衡仍然可以调用。
存在漏洞的代码示例 (BakerFi):
function rebalance(IVault.RebalanceCommand[] calldata commands)
external nonReentrant onlyRole(VAULT_MANAGER_ROLE) { // 没有 whenNotPaused!
// 可以在用户无法取款时收取性能费用
}
影响:在用户被锁定时收取性能费用。
缓解措施:将 whenNotPaused
修饰符添加到重新平衡函数中。
154. 路由器存款限制 DoS (BakerFi)
模式:VaultRouter 本身作为 msg.sender 受到存款限制。
存在漏洞的代码示例 (BakerFi):
// 在金库中:
if (maxDepositLocal > 0) {
uint256 depositInAssets = (balanceOf(msg.sender) * _ONE) / tokenPerAsset();
// msg.sender 是 VaultRouter!
}
攻击:通过路由器存款直到路由器达到限制,阻止所有路由器存款。
影响:路由器存款功能的完全 DoS。
缓解措施:免除路由器的限制或跟踪实际的存款人。
155. 零金额策略撤回部署 DoS (BakerFi)
模式:没有资产的策略会导致多重策略金库中的取款 DoS。
存在漏洞的代码示例 (BakerFi):
function _deallocateAssets(uint256 amount) internal returns (uint256 totalUndeployed) {
for (uint256 i = 0; i < strategiesLength; i++) {
uint256 fractAmount = (amount * currentAssets[i]) / totalAssets;
totalUndeployed += IStrategy(_strategies[i]).undeploy(fractAmount); // 如果为 0 则回滚!
}
}
影响:如果任何策略的资产为零,则所有取款都会被阻止。
缓解措施:跳过提款金额为零的策略。
156. Morpho 策略利息计算错误 (BakerFi)
模式:assetsMax
计算在撤回部署中缺少应计利息。
存在漏洞的代码示例 (BakerFi):
function _undeploy(uint256 amount) internal override returns (uint256) {
uint256 totalSupplyAssets = _morpho.totalSupplyAssets(id); // 过时!
uint256 totalSupplyShares = _morpho.totalSupplyShares(id);
uint256 assetsMax = shares.toAssetsDown(totalSupplyAssets, totalSupplyShares);
// 但是 amount 包含来自 expectedSupplyAssets 的利息!
}
影响:不正确的选择分支,导致用户收到额外的资金。
缓解措施:使用 expectedSupplyAssets
进行 assetsMax
计算。
157. 暂停的第三方策略锁定 (BakerFi)
模式:在多重策略金库中,无法处理暂停的第三方协议。
存在漏洞的代码示例 (BakerFi):
// 所有操作都尝试与所有策略进行交互
function _deallocateAssets(uint256 amount) internal {
for (uint256 i = 0; i < strategiesLength; i++) {
// 如果策略的底层协议已暂停,则回滚
totalUndeployed += IStrategy(_strategies[i]).undeploy(fractAmount);
}
}
影响:单个暂停的协议会锁定整个多重策略金库。
缓解措施:为暂停的策略添加紧急排除机制。
158. 移除最后一个策略导致除以零 (BakerFi)
模式:移除最后一个策略会导致除以零。
存在漏洞的代码示例 (BakerFi):
function removeStrategy(uint256 index) external {
_totalWeight -= _weights[index];
_weights[index] = 0; // 现在 _totalWeight = 0
if (strategyAssets > 0) {
IStrategy(_strategies[index]).undeploy(strategyAssets);
_allocateAssets(strategyAssets); // 除以 _totalWeight = 0!
}
}
影响:如果最后一个策略有资产,则无法移除。
缓解措施:特殊处理最后一个策略的移除或阻止它。
159. Morpho 策略中的错误 Token 配置 (BakerFi)
模式:允许 Morpho 市场中不匹配的资产和贷款 token。
存在漏洞的代码示例 (BakerFi):
constructor(address asset_, address morphoBlue, Id morphoMarketId) {
_asset = asset_; // 可以与市场的 loanToken 不同!
_marketParams = _morpho.idToMarketParams(morphoMarketId);
if (!ERC20(asset_).approve(morphoBlue, type(uint256).max)) {
// 批准错误的 token!
}
}
影响:由于 token 不匹配,策略完全不可用。
缓解措施:验证 asset_ == _marketParams.loanToken
。
160. 策略撤回部署返回值不匹配 (BakerFi)
模式:策略返回请求的金额,而不是实际提取的金额。
存在漏洞的代码示例 (BakerFi):
function undeploy(uint256 amount) external returns (uint256 undeployedAmount) {
uint256 withdrawalValue = _undeploy(amount); // 实际金额
ERC20(_asset).safeTransfer(msg.sender, withdrawalValue);
return amount; // 错误:应该返回 withdrawalValue!
}
影响:金库收到错误的金额,导致转账失败。
缓解措施:返回实际提取的金额。
161. 非白名单接收者绕过 (BakerFi)
模式:白名单限制仅检查调用者,不检查接收者。
存在漏洞的代码示例 (BakerFi):
function deposit(uint256 assets, address receiver) public override onlyWhiteListed {
// 仅检查 msg.sender 是否在白名单中
// receiver 可以是任何人!
return _depositInternal(assets, receiver);
}
影响:非白名单用户可以通过路由器接收份额和提取资金。
缓解措施:检查调用者和接收者是否都在白名单中。
162. 调度命令解析错误 (BakerFi)
模式:用于检查 PULL_TOKEN 命令的变量错误。
存在漏洞的代码示例 (BakerFi):
} else if (action == Commands.PULL_TOKEN) { // 应该是 actionToExecute!
output = _handlePullToken(data, callStack, inputMapping);
}
影响:带有输入映射的 PULL_TOKEN 会导致回滚。
缓解措施:使用 actionToExecute
而不是 action
。
163. 奖励计算费用绕过 (LoopFi)
模式:奖励费用未从用户金额中扣除,导致 DoS 或资金盗窃。
存在漏洞的代码示例 (LoopFi):
function claim(uint256[] memory amounts, uint256 maxAmountIn) external returns (uint256 amountIn) {
// 分发 BAL 奖励
IERC20(BAL).safeTransfer(_config.lockerRewards, (amounts[0] * _config.lockerIncentive) / INCENTIVE_BASIS);
IERC20(BAL).safeTransfer(msg.sender, amounts[0]); // 发送全部金额,未扣除费用!
}
影响:
- 当合约没有足够的奖励时,DoS
- 用户收到额外的奖励,从其他人那里窃取
缓解措施:在发送给用户之前扣除费用。
164. 清算罚金未应用于抵押品 (LoopFi)
模式:清算人收到全部抵押品价值,尽管存在罚金机制。
存在漏洞的代码示例 (LoopFi):
function liquidatePosition(address owner, uint256 repayAmount) external {
uint256 takeCollateral = wdiv(repayAmount, discountedPrice);
uint256 deltaDebt = wmul(repayAmount, liqConfig_.liquidationPenalty);
uint256 penalty = wmul(repayAmount, WAD - liqConfig_.liquidationPenalty);
// 抵押品计算未考虑罚金!
}
影响:尽管存在罚金机制,自清算仍有利可图。
缓解措施:将罚金应用于抵押品金额计算。
165. 零利率配额利息绕过 (LoopFi)
模式:新的配额 token 的利率为零,允许无息借款。
存在漏洞的代码示例 (LoopFi):
function addQuotaToken(address token) external override gaugeOnly {
quotaTokensSet.add(token); // 默认情况下,利率为 0
totalQuotaParams[token].cumulativeIndexLU = 1;
emit AddQuotaToken(token);
}
影响:用户可以以零利率借款,直到利率更新。
缓解措施:添加配额 token 时设置初始利率。
166. 访问控制中缺少角色设置 (LoopFi)
模式:AccessControl 角色从未初始化,导致永久 DoS。
存在漏洞的代码示例 (LoopFi):
contract AuraVault inherits AccessControl {
// 构造函数未调用 _setupRole()
// 永远无法授予任何角色!
}
影响:
- 永久无法访问配置函数
- 声称函数总是还原(发送到 address(0))
缓解措施:在构造函数中初始化角色。
167. 金库赎回中的份额/资产混淆 (LoopFi)
模式:在奖励池操作中使用份额作为资产。
存在漏洞的代码示例 (LoopFi):
function redeem(uint256 shares, address receiver, address owner) public override returns (uint256) {
uint256 assets = IPool(rewardPool).redeem(shares, address(this), address(this));
// rewardPool 期望资产,而不是份额!
}
影响:用户收到的资产少于应得的资产。
缓解措施:在调用 rewardPool 之前将份额转换为资产。
168. 抢先交易清算 DoS (LoopFi)
模式:借款人可以通过最小的还款来抢先交易清算。
存在漏洞的代码示例 (LoopFi):
function liquidatePosition(address owner, uint256 repayAmount) external {
// 如果 debt < repayAmount, calcDecrease 下溢
(newDebt, ...) = calcDecrease(deltaDebt, debtData.debt, ...);
}
攻击:在清算前偿还 1 wei 以导致下溢。
影响:清算被永久阻止。
缓解措施:处理 repayAmount > 当前债务的情况。
169. 通过周期操纵利率 (LoopFi)
模式:快速借入-偿还周期会复合利率。
存在漏洞的代码示例 (LoopFi):
function _updateBaseInterest(...) internal {
if (block.timestamp != lastBaseInterestUpdate_) {
_baseInterestIndexLU = _calcBaseInterestIndex(lastBaseInterestUpdate_).toUint128();
// 每次更新都会复合!
}
}
影响:所有借款人的利率更高,而攻击者没有风险。
缓解措施:添加借款费用或最短贷款期限。
170. vestTokens 中的奖励覆盖 (LoopFi)
模式:缺少状态更新会导致先前的奖励被擦除。
存在漏洞的代码示例 (LoopFi):
function vestTokens(address user, uint256 amount, bool withPenalty) external {
if (user == address(this)) {
_notifyReward(address(rdntToken), amount); // 缺少 _updateReward!
return;
}
}
影响:用户丢失所有先前累积的奖励。
缓解措施:在 _notifyReward
之前调用 _updateReward
。
171. 清算期间的头寸增加 (LoopFi)
模式:减少杠杆操作中缺少头寸地址。
存在漏洞的代码示例 (LoopFi):
function _onDecreaseLever(LeverParams memory leverParams, uint256 subCollateral) internal returns (uint256) {
uint256 withdrawnCollateral = ICDPVault(leverParams.vault).withdraw(address(this), subCollateral);
// 应该从 leverParams.position 中提取!
}
影响:减少杠杆操作总是恢复。
缓解措施:使用正确的头寸地址进行提款。
172. 利率计算不匹配 (LoopFi)
模式:池使用单利,而头寸使用复利。
存在漏洞的代码示例 (LoopFi):
// 池:borrowed * rate * time
function _calcBaseInterestAccrued(uint256 timestamp) private view returns (uint256) {
return (_totalDebt.borrowed * baseInterestRate().calcLinearGrowth(timestamp)) / RAY;
}
// 头寸:通过索引更新复合
function _calcBaseInterestIndex(uint256 timestamp) private view returns (uint256) {
return (_baseInterestIndexLU * (RAY + baseInterestRate().calcLinearGrowth(timestamp))) / RAY;
}
影响:
- 借款人支付的费用高于预期
- 协议会计不一致
- 最终提款可能会恢复
缓解措施:对齐利息计算方法。
173. 清算债务精度损失 (LoopFi)
模式:由于时机,几乎不可能完全清算债务。
存在漏洞的代码示例 (LoopFi):
function liquidatePosition(address owner, uint256 repayAmount) external {
if (deltaDebt == maxRepayment) {
// 必须精确到秒!
newDebt = 0;
} else {
// 如果 deltaDebt > maxRepayment,则恢复
}
}
影响:随着时间的推移累积坏账。
缓解措施:允许略微超额支付并提供退款机制。
174. 坏账利息损失 (LoopFi)
模式:在坏账清算期间,利息未计入 LP。
存在漏洞的代码示例 (LoopFi):
function liquidatePositionBadDebt(address owner, uint256 repayAmount) external {
pool.repayCreditAccount(debtData.debt, 0, loss); // profit = 0!
// debtData.accruedInterest 丢失
}
影响:LP 质押者会损失坏账头寸的应计利息。
缓解措施:将应计利息作为利润参数包括在内。
175. 闪电贷费用会计错误 (LoopFi)
模式:闪电贷费用被错误地处理为债务偿还。
存在漏洞的代码示例 (LoopFi):
function flashLoan(...) external returns (bool) {
pool.repayCreditAccount(total - fee, fee, 0);
// 应该使用 mintProfit() 来收取费用!
}
影响:WETH 锁定在池中,提款失败。
缓解措施:使用 mintProfit()
来收取闪电贷费用。
176. Withdraw 中的无限循环 (LoopFi)
模式:零金额时,循环计数器未递增。
存在漏洞的代码示例 (LoopFi):
for (i = 0; ; ) {
uint256 earnedAmount = _userEarnings[_address][i].amount;
if (earnedAmount == 0) continue; // i 永远不会递增!
}
影响:提款被永久阻止。
缓解措施:在继续之前递增计数器。
177. 奖励分配干扰 (LoopFi)
模式:少量金额会重置奖励分配周期。
存在漏洞的代码示例 (LoopFi):
function _notifyUnseenReward(address token) internal {
uint256 unseen = IERC20(token).balanceOf(address(this)) - r.balance;
if (unseen > 0) {
_notifyReward(token, unseen); // 即使是 1 wei 也会重置!
}
}
攻击:重复发送 1 wei 以延长归属期。
影响:合法的奖励被无限期延迟。
缓解措施:实施最低奖励阈值。
178. 通过部分清算操纵 CDP 清算 (LoopFi)
模式:部分清算可以暂时使不安全的头寸安全,从而延迟必要的完全清算。
存在漏洞的代码示例 (LoopFi):
function liquidatePosition(address owner, uint256 repayAmount) external whenNotPaused {
// ... (验证和状态加载)
if (_isCollateralized(calcTotalDebt(debtData), wmul(position.collateral, spotPrice_), config.liquidationRatio))
revert CDPVault__liquidatePosition_notUnsafe();
// ... (清算计算)
position = _modifyPosition(owner, position, newDebt, newCumulativeIndex, -toInt256(takeCollateral), totalDebt);
// 部分清算后,头寸可以暂时变为安全
}
影响:
- 自我清算可以保护头寸免受完全清算
- 如果抵押品继续下跌,协议损失会增加
- 违背了预期的清算机制
缓解措施:一旦头寸超过阈值,就将其标记为不安全,仅在抵押品存款时取消标记。
179. 在头寸偿还中使用错误的金额 (LoopFi)
模式:在头寸操作中使用用户指定的金额,而不是实际交换的金额。
存在漏洞的代码示例 (LoopFi):
function _repay(address vault, address position, CreditParams calldata creditParams, PermitParams calldata permitParams) internal {
uint256 amount = creditParams.amount;
if (creditParams.auxSwap.assetIn != address(0)) {
amount = _transferAndSwap(creditParams.creditor, creditParams.auxSwap, permitParams);
// amount 现在是交换的金额
}
// ... 但仍然使用 creditParams.amount!
ICDPVault(vault).modifyCollateralAndDebt(position, address(this), address(this), 0, -toInt256(creditParams.amount));
}
影响:当交换返回不同的金额时,由于余额不足,交易会恢复。
缓解措施:对金库操作使用实际交换的金额。
180. Balancer EXACT_OUT 的交换 Token 计算错误 (LoopFi)
模式:无论交换类型如何,总是为 Balancer 交换返回最后一个资产。
存在漏洞的代码示例 (LoopFi):
function getSwapToken(SwapParams memory swapParams) public pure returns (address token) {
if (swapParams.swapProtocol == SwapProtocol.BALANCER) {
(, address[] memory primarySwapPath) = abi.decode(swapParams.args, (bytes32, address[]));
token = primarySwapPath[primarySwapPath.length - 1]; // EXACT_OUT 的错误!
}
}
影响:EXACT_OUT 交换的 token 识别错误,其中资产按相反的顺序排列。
缓解措施:检查交换类型并返回适当的 token:
if (swapParams.swapType == SwapType.EXACT_OUT) token = primarySwapPath[0];
else token = primarySwapPath[primarySwapPath.length - 1];
181. 硬编码的通货膨胀保护时间 (LoopFi)
模式:使用硬编码的时间戳而不是相对时间进行奖励分配。
存在漏洞的代码示例 (LoopFi):
uint256 private constant INFLATION_PROTECTION_TIME = 1749120350; // 固定时间戳!
function claim(uint256[] memory amounts, uint256 maxAmountIn) external returns (uint256 amountIn) {
// ...
if (block.timestamp <= INFLATION_PROTECTION_TIME) {
IERC20(AURA).safeTransfer(_config.lockerRewards, (amounts[1] * _config.lockerIncentive) / INCENTIVE_BASIS);
IERC20(AURA).safeTransfer(msg.sender, amounts[1]);
}
}
影响:奖励分配窗口每天都在缩小;如果部署得太晚,则不会分配 AURA 奖励。
缓解措施:在构造函数中设置保护时间:
uint256 private immutable INFLATION_PROTECTION_TIME;
constructor(...) {
INFLATION_PROTECTION_TIME = block.timestamp + 365 days;
}
182. 头寸操作杠杆函数总是失败 (LoopFi)
模式:在同一交易中两次存入抵押品会导致恢复。
存在漏洞的代码示例 (LoopFi):
function _onIncreaseLever(LeverParams memory leverParams, uint256 upFrontAmount, uint256 addCollateralAmount)
internal override returns (uint256) {
// 首次存款
return ICDPVault(leverParams.vault).deposit(address(this), addCollateralAmount);
}
// 稍后在 onFlashLoan 中:
ICDPVault(leverParams.vault).modifyCollateralAndDebt(
leverParams.position,
address(this),
address(this),
toInt256(collateral), // 尝试再次存款!
toInt256(addDebt)
);
影响:增加杠杆功能对于 ERC4626 头寸完全被破坏。
缓解措施:在 _onIncreaseLever
中返回金额而不是存款。
183. 池操作加入参数损坏 (LoopFi)
模式:在更新 Balancer 池加入参数时,数组索引不正确。
存在漏洞的代码示例 (LoopFi):
function updateLeverJoin(...) external view returns (PoolActionParams memory outParams) {
// ...
for (uint256 i = 0; i < len; ) {
uint256 assetIndex = i - (skipIndex ? 1 : 0);
if (assets[i] == joinToken) {
maxAmountsIn[i] = joinAmount;
assetsIn[assetIndex] = joinAmount; // 如果找不到 BPT,则索引错误!
}
// ...
}
}
影响:由于资产和金额之间的数组索引不匹配,Balancer 加入失败。
缓解措施:在处理之前,从 Balancer 金库中正确识别池 token。
184. 减少杠杆中的错误抵押品金额 (LoopFi)
模式:更新从金库中提取的抵押品时,token 金额错误。
存在漏洞的代码示例 (LoopFi):
function _onDecreaseLever(LeverParams memory leverParams, uint256 subCollateral)
internal override returns (uint256 tokenOut) {
uint256 withdrawnCollateral = ICDPVault(leverParams.vault).withdraw(address(this), subCollateral);
tokenOut = IERC4626(leverParams.collateralToken).redeem(withdrawnCollateral, address(this), address(this));
if (leverParams.auxAction.args.length != 0) {
bytes memory exitData = _delegateCall(address(poolAction),
abi.encodeWithSelector(poolAction.exit.selector, leverParams.auxAction));
tokenOut = abi.decode(exitData, (uint256)); // 用池退出金额更新!
}
}
影响:发送给用户的金额错误,资金卡在合约中。
缓解措施:跟踪实际的 token 余额,而不是依赖返回值。
185. Balancer 退出返回错误的金额 (LoopFi)
模式:返回接收者的总余额,而不是从退出收到的金额。
存在漏洞的代码示例 (LoopFi):
function _balancerExit(PoolActionParams memory poolActionParams) internal returns (uint256 retAmount) {
// ... 执行退出 ...
return IERC20(assets[outIndex]).balanceOf(address(poolActionParams.recipient)); // 总余额!
}
影响:膨胀的返回值会导致交易恢复,因为合约尝试发送超过收到的金额。
缓解措施:计算退出前后余额差异。
186. 已弃用的 Chainlink 函数用法 (LoopFi)
模式:使用 Chainlink 不再支持的已弃用的 answeredInRound
。
存在漏洞的代码示例 (LoopFi):
function _fetchAndValidate(address priceFeed) private view returns (uint256 answer) {
(, int256 _answer, , uint256 updatedAt, uint80 answeredInRound) = AggregatorV3Interface(priceFeed)
.latestRoundData();
// answeredInRound 已弃用!
}
影响:潜在的不正确的价格验证或未来的重大更改。
缓解措施:删除 answeredInRound
的用法。
187. 最小份额干扰攻击 (LoopFi)
模式:攻击者可以强制用户将资金锁定或 DoS 存款。
存在漏洞的代码示例 (LoopFi):
function _checkMinShares() internal view {
uint256 _totalSupply = totalSupply();
if(_totalSupply > 0 && _totalSupply < MIN_SHARES) revert MinSharesViolation();
}
攻击场景:
- 在每个用户之后存入 1 wei 以阻止完全取款
- 将少量金额转移到合约以使存款需要大量金额
影响:最后的提款人会损失价值 MIN_SHARES 的资金或存款被阻止。
缓解措施:协议应资助初始份额并删除检查。
188. 在清算中未应用 Token 比例 (LoopFi)
模式:将内部金额而不是按比例缩放的金额发送给清算人。
存在漏洞的代码示例 (LoopFi):
function liquidatePosition(address owner, uint256 repayAmount) external {
// ... 计算 takeCollateral (内部金额) ...
token.safeTransfer(msg.sender, takeCollateral); // 应该按 tokenScale 缩放!
}
// 与 withdraw 比较:
function withdraw(address to, uint256 amount) external {
uint256 amount = wmul(abs(deltaCollateral), tokenScale); // 正确缩放
token.safeTransfer(collateralizer, amount);
}
影响:对于 16 位小数的 token,清算人收到的 token 是预期的 100 倍。
缓解措施:在转移之前应用 tokenScale:wmul(takeCollateral, tokenScale)
。
189. 金库操作中缺少滑点保护 (LoopFi)
模式:在存款/铸造函数中没有最小份额/资产参数。
存在漏洞的代码示例 (LoopFi):
function deposit(uint256 assets, address receiver) public virtual override returns (uint256) {
uint256 shares = previewDeposit(assets);
_deposit(_msgSender(), receiver, assets, shares);
// 没有检查 shares >= minShares!
}
影响:用户容易受到三明治攻击,收到的份额少于预期。
缓解措施:添加滑点参数:
function deposit(uint256 assets, uint256 minShares, address receiver) public virtual override returns (uint256) {
uint256 shares = previewDeposit(assets);
require(shares >= minShares, "Insufficient shares");
// ...
}
190. 通过抢先交易进行 Permit DoS (LoopFi)
模式:攻击者可以提取 permit 签名并首先使用它。
存在漏洞的代码示例 (LoopFi):
function _transferFrom(address token, address from, address to, uint256 amount, PermitParams memory params) internal {
if (params.approvalType == ApprovalType.PERMIT) {
IERC20Permit(token).safePermit(from, to, params.approvalAmount, params.deadline, params.v, params.r, params.s);
IERC20(token).safeTransferFrom(from, to, amount);
}
}
攻击:使用相同的 permit 参数进行抢先交易,导致原始交易由于 nonce 递增而失败。
影响:基于 permit 的转移的 DoS。
缓解措施:使用 try-catch 并检查现有 allowance 作为后备方案。
191. 暂停机制绕过 (LoopFi)
模式:用户可以通过直接调用底层函数来绕过暂停。
存在漏洞的代码示例 (LoopFi):
function deposit(address to, uint256 amount) external whenNotPaused returns (uint256 tokenAmount) {
tokenAmount = wdiv(amount, tokenScale);
int256 deltaCollateral = toInt256(tokenAmount);
modifyCollateralAndDebt({...}); // 这是公开的!
}
function modifyCollateralAndDebt(...) public { // 没有 whenNotPaused!
// 用户可以直接调用它
}
影响:暂停机制无法有效地保护协议。
缓解措施:将 whenNotPaused
添加到所有更改状态的函数。
192. 坏账中的不正确损失计算 (LoopFi)
模式:将未收到的利息包括在损失计算中。
存在漏洞的代码示例 (LoopFi):
function liquidatePositionBadDebt(address owner, uint256 repayAmount) external {
// ...
uint256 loss = calcTotalDebt(debtData) - repayAmount; // 包括 accruedInterest!
pool.repayCreditAccount(debtData.debt, 0, loss);
}
function calcTotalDebt(DebtData memory debtData) internal pure returns (uint256) {
return debtData.debt + debtData.accruedInterest; // 利息是利润,而不是本金
}
影响:协议会燃烧额外的国库份额,这些份额代表从未获得的“损失”利润。
缓解措施:将损失计算为 debtData.debt - repayAmount
。
193. 闪电贷费用绕过 (LoopFi)
模式:在闪电贷计算中未考虑协议费用。
存在漏洞的代码示例 (LoopFi):
function decreaseLever(LeverParams memory leverParams) external onlyOwner(leverParams.position) {
uint256 fee = flashlender.flashFee(leverParams.singleSwap.tokenIn, leverParams.primarySwap.amount);
uint256 loanAmount = leverParams.primarySwap.amount - fee; // 应该添加费用!
// ...
}
影响:当#### 196. 头寸行为 Token 返回比例错误 (LoopFi) 模式: 头寸行为函数返回的金额比例错误。
有漏洞的代码示例 (LoopFi):
function _onWithdraw(address vault, address position, address /*dst*/, uint256 amount)
internal override returns (uint256) {
return ICDPVault(vault).withdraw(position, amount); // 返回 WAD 比例
}
// 但是调用者期望 token 比例:
function _withdraw(...) internal returns (uint256) {
uint256 collateral = _onWithdraw(...); // 获取 WAD
IERC20(collateralParams.targetToken).safeTransfer(
collateralParams.collateralizer,
collateral // 使用 WAD 金额进行 token 转移!
);
}
影响: 由于余额不足,非 18 位小数的 token 提款失败。
缓解措施: 将返回值转换为适当的比例。
197. 奖励池资格检查竞争条件 (LoopFi)
模式: 在刷新之前检查资格,导致不符合条件的用户可以申领。
有漏洞的代码示例 (LoopFi):
function claim(address _user, address[] memory _tokens) public whenNotPaused {
if (eligibilityMode != EligibilityModes.DISABLED) {
if (!eligibleDataProvider.isEligibleForRewards(_user)) revert EligibleRequired();
checkAndProcessEligibility(_user, true, true); // 检查之后才刷新!
}
}
影响: 由于价格变动而失去资格的用户仍然可以申领奖励。
缓解措施: 在资格检查之前调用 checkAndProcessEligibility
。
198. 手动停止排放余额同步问题 (LoopFi)
模式: 将余额设置为零会中断后续的资格更新。
有漏洞的代码示例 (LoopFi):
function manualStopEmissionsFor(address _user, address[] memory _tokens) public isWhitelisted {
// 将所有余额设置为 0
user.amount = 0;
user.rewardDebt = 0;
pool.totalSupply = newTotalSupply;
}
// 之后当用户符合资格时:
function handleActionAfter(address _user, uint256 _balance, uint256 _totalSupply) external {
if (isCurrentlyEligible && lastEligibleStatus) {
_handleActionAfterForToken(msg.sender, _user, _balance, _totalSupply);
// 仅更新一个 vault,其他 vault 仍为 0!
}
}
影响: 用户在重新获得资格后,仅在一个 vault 上赚取奖励。
缓解措施: 当资格状态改变时更新所有 vault 的余额。
199. 奖励时间计算差异 (LoopFi)
模式: 池更新时间戳可能与大规模更新的时间戳不同。
有漏洞的代码示例 (LoopFi):
function _updatePool(VaultInfo storage pool, uint256 _totalAllocPoint) internal {
uint256 timestamp = block.timestamp;
uint256 endReward = endRewardTime();
if (endReward <= timestamp) {
timestamp = endReward; // 池时间戳 < lastAllPoolUpdate
}
pool.lastRewardTime = timestamp;
}
// 之后:
function endRewardTime() public returns (uint256) {
uint256 newEndTime = (unclaimedRewards + extra) / rewardsPerSecond + lastAllPoolUpdate;
// 使用 lastAllPoolUpdate,它可能 > 池时间戳
}
影响: 池可以申领超出耗尽时间的奖励。
缓解措施: 同步时间戳或调整 endTime 计算。
200. 存款辅助交换验证错误 (LoopFi)
模式: 不正确的验证阻止了有效的交换操作。
有漏洞的代码示例 (LoopFi):
function _deposit(...) internal returns (uint256) {
if (collateralParams.auxSwap.assetIn != address(0)) {
if (
collateralParams.auxSwap.assetIn != collateralParams.targetToken || // 错误的检查!
collateralParams.auxSwap.recipient != address(this)
) revert PositionAction__deposit_InvalidAuxSwap();
}
}
影响: 无法在存款前交换 token,该功能完全不可用。
缓解措施: 删除不正确的等式检查。
201. ERC4626 头寸行为提款地址不匹配 (LoopFi)
模式: 从头寸行为中的错误地址提款。
有漏洞的代码示例 (LoopFi):
function _onWithdraw(address vault, address /*position*/, address dst, uint256 amount)
internal override returns (uint256) {
uint256 collateralWithdrawn = ICDPVault(vault).withdraw(address(this), amount);
// 应该使用 position 参数!
}
影响: 无法从合约本身的头寸以外的其他头寸提款。
缓解措施: 使用 position 参数进行提款。
202. 交换中缺少原生 ETH 支持 (LoopFi)
模式: 交换函数未将 msg.value 传递给外部协议。
有漏洞的代码示例 (LoopFi):
function swap(...) external payable { // 函数是 payable 的
if (swapParams.swapProtocol == SwapProtocol.BALANCER) {
balancerVault.batchSwap( // 缺少: {value: msg.value}
// ...
);
}
}
影响: 无法使用原生 ETH 作为交换的输入。
缓解措施: 在外部调用中传递 msg.value。
203. 排放计划更新时序问题 (LoopFi)
模式: 奖励分配在计划转换期间使用旧的速率。
有漏洞的代码示例 (LoopFi):
function setScheduledRewardsPerSecond() internal {
if (i > emissionScheduleIndex) {
emissionScheduleIndex = i;
_massUpdatePools(); // 使用旧的 rewardsPerSecond!
rewardsPerSecond = uint256(emissionSchedule[i - 1].rewardsPerSecond);
}
}
影响: 计划变更期间奖励分配不正确。
缓解措施: 在过渡期间使用新旧两种速率计算奖励。
204. Vault 操作期间的 ERC4626 滑点漏洞 (LoopFi)
模式: 在头寸行为中与 ERC4626 vault 交互时没有滑点保护。
有漏洞的代码示例 (LoopFi):
function _onDeposit(address vault, address /*position*/, address src, uint256 amount)
internal override returns (uint256) {
if (src != collateral) {
IERC20(underlying).forceApprove(collateral, amount);
amount = IERC4626(collateral).deposit(amount, address(this)); // 没有滑点检查!
}
return ICDPVault(vault).deposit(address(this), amount);
}
影响: 用户容易受到三明治攻击和汇率操纵。
缓解措施: 添加最小份额参数和验证。
205. 通过自交易进行的折扣费用利用 (Ditto)
模式: 攻击者可以触发折扣费用,并通过 vault 所有权申领,从而从该机制中获利。
有漏洞的代码示例 (Ditto):
function _matchIsDiscounted(MTypes.HandleDiscount memory h) external onlyDiamond {
// ...
if (pctOfDiscountedDebt > C.DISCOUNT_THRESHOLD && !LibTStore.isForcedBid()) {
// 根据总债务计算费用
uint256 discountPenaltyFee = uint64(LibAsset.discountPenaltyFee(Asset));
uint256 ercDebtMinusTapp = h.ercDebt - Asset.ercDebtFee;
// 将费用作为新债务铸造
uint104 newDebt = uint104(ercDebtMinusTapp.mul(discountPenaltyFee));
Asset.ercDebt += newDebt;
// 铸造到 yDUSD vault
IERC20(h.asset).mint(s.yieldVault[h.asset], newDebt);
}
}
攻击场景:
- 攻击者拥有 yDUSD vault 的重要份额
- 以折扣价创建交易以触发费用
- 铸造到 vault 的费用会增加份额价值
- 2 天后,利润超过交易损失
影响: 攻击者可以通过利用费用机制来免费铸造 DUSD。
缓解措施: 根据实际交易金额而不是总债务来计算费用。
206. 通过取消空头交易使用更少的抵押品铸造 DUSD (Ditto)
模式: 在取消部分空头头寸时,使用过时的价格和较低的 CR 来铸造具有不足抵押品的 DUSD。
有漏洞的代码示例 (Ditto):
function cancelShort(address asset, uint16 id) internal {
// ...
if (shortRecord.status == SR.PartialFill) {
uint256 minShortErc = LibAsset.minShortErc(Asset);
if (shortRecord.ercDebt < minShortErc) {
uint88 debtDiff = uint88(minShortErc - shortRecord.ercDebt);
// 使用过时的价格和可能较低的 CR
uint88 collateralDiff = shortOrder.price.mulU88(debtDiff).mulU88(cr);
// 以低于要求的抵押品铸造 DUSD
s.assetUser[asset][shorter].ercEscrowed += debtDiff;
}
}
}
影响: 用户可以以低于 100% CR 的价格铸造 DUSD,从而创建无抵押的稳定币。
缓解措施: 使用当前的预言机价格并强制执行最低 CR。
207. yDUSD Vault 直接铸造会计错误 (Ditto)
模式: 直接铸造到 yDUSD vault 会增加余额,而不更新 totalSupply,从而破坏份额计算。
有漏洞的代码示例 (Ditto):
// 发生折扣时:
IERC20(h.asset).mint(s.yieldVault[h.asset], newDebt); // 增加余额
// 之后当用户存款时:
function previewDeposit(uint256 assets) public view returns (uint256) {
return _convertToShares(assets, Math.Rounding.Down);
}
function _convertToShares(uint256 assets, Math.Rounding rounding) internal view returns (uint256) {
// shares = assets * (0 + 1) / newDebt = 0 (向下舍入)
return assets.mulDiv(totalSupply() + 10 ** _decimalsOffset(), totalAssets() + 1, rounding);
}
影响: 存款者收到 0 份额并损失所有存入的资产。
缓解措施: 实施适当的自动复利机制,如 xERC4626。
208. yDUSD 提款时间锁绕过 (Ditto)
模式: 用户可以使用另一个帐户的提款提案来绕过 7 天的时间锁。
有漏洞的代码示例 (Ditto):
function withdraw(uint256 assets, address receiver, address owner) public override returns (uint256) {
// 使用 msg.sender 的提案
WithdrawStruct storage withdrawal = withdrawals[msg.sender];
// 但是从所有者的帐户提款
_withdraw(_msgSender(), receiver, owner, amountProposed, shares);
}
攻击:
- 攻击者批准 shares 给同谋
- 同谋有现有的提款提案
- 攻击者使用同谋的时间锁状态提款
影响: 完全绕过 7 天的提款时间锁。
缓解措施: 要求 msg.sender == owner
进行提款。
209. 免费 DUSD 铸造和清算奖励利用 (Ditto)
模式: 将 decreaseCollateral 和 cancelShort 与 CR < 1 结合起来,以创建可清算的头寸来获利。
有漏洞的代码示例 (Ditto):
// 创建 CR < 1 的空头
function createLimitShort(asset, price, minShortErc, orderHintArray, shortHintArray, 70); // 70% CR
// 在部分填补后:
decreaseCollateral(shortRecordId, amount); // 删除为 minShortErc 添加的抵押品
cancelShort(orderId); // 仅以 70% 的抵押品铸造 DUSD
// 现在可以清算头寸
影响: 攻击者铸造免费 DUSD 并赚取清算奖励。
缓解措施: 检查操作后的结果 CR 是否高于清算阈值。
210. 资金提取中的任意调用漏洞 (JOJO)
模式: 提取期间用户控制的外部调用允许任意合约交互。
有漏洞的代码示例 (JOJO):
function _withdraw(..., address to, ..., bytes memory param) private {
// ... 提款逻辑 ...
if (param.length != 0) {
require(Address.isContract(to), "target is not a contract");
(bool success,) = to.call(param); // 有漏洞: 任意调用
if (success == false) {
assembly {
let ptr := mload(0x40)
let size := returndatasize()
returndatacopy(ptr, 0, size)
revert(ptr, size)
}
}
}
}
攻击向量: 攻击者可以执行 1 wei 到 USDC 合约的提款,并传递 calldata 将任意 USDC 金额转移给自己。
影响: 完全耗尽 JOJODealer 的所有资金。
缓解措施: 列入允许的合约的白名单或删除任意调用功能。
211. FundingRateArbitrage 舍入利用 (JOJO)
模式: 提款计算中不正确的舍入方向允许耗尽合约。
有漏洞的代码示例 (JOJO):
function requestWithdraw(uint256 repayJUSDAmount) external {
jusdOutside[msg.sender] -= repayJUSDAmount;
uint256 index = getIndex();
uint256 lockedEarnUSDCAmount = jusdOutside[msg.sender].decimalDiv(index); // 向下舍入!
require(
earnUSDCBalance[msg.sender] >= lockedEarnUSDCAmount,
"lockedEarnUSDCAmount is bigger than earnUSDCBalance"
);
withdrawEarnUSDCAmount = earnUSDCBalance[msg.sender] - lockedEarnUSDCAmount;
}
攻击场景:
- 攻击者存款并膨胀份额价格
- 进行小额存款以获得份额
- 以最少的 JUSD 偿还提款
- 由于向下舍入,获得比应得的更多的 USDC
- 重复直到合约耗尽
影响: 完全耗尽合约中的 JUSD。
缓解措施: 对于 lockedEarnUSDCAmount
,向上舍入而不是向下舍入。
212. 利率计算不匹配 (JOJO)
模式: getTRate()
和 accrueRate()
使用不同的公式导致计算差异。
有漏洞的代码示例 (JOJO):
function accrueRate() public {
uint256 currentTimestamp = block.timestamp;
if (currentTimestamp == lastUpdateTimestamp) {
return;
}
uint256 timeDifference = block.timestamp - uint256(lastUpdateTimestamp);
tRate = tRate.decimalMul((timeDifference * borrowFeeRate) / Types.SECONDS_PER_YEAR + 1e18);
lastUpdateTimestamp = currentTimestamp;
}
function getTRate() public view returns (uint256) {
uint256 timeDifference = block.timestamp - uint256(lastUpdateTimestamp);
return tRate + (borrowFeeRate * timeDifference) / Types.SECONDS_PER_YEAR; // 不同的公式!
}
影响: 所有依赖函数(包括清算、闪电贷和抵押品检查)中的计算不正确。
缓解措施: 在两个函数中使用一致的计算公式。
213. 使用错误地址的提款请求 (JOJO)
模式: 在提款请求中使用 msg.sender
而不是 from
参数。
有漏洞的代码示例 (JOJO):
function requestWithdraw(
Types.State storage state,
address from,
uint256 primaryAmount,
uint256 secondaryAmount
) external {
require(isWithdrawValid(state, msg.sender, from, primaryAmount, secondaryAmount), Errors.WITHDRAW_INVALID);
state.pendingPrimaryWithdraw[msg.sender] = primaryAmount; // 应该是 'from'!
state.pendingSecondaryWithdraw[msg.sender] = secondaryAmount;
state.withdrawExecutionTimestamp[msg.sender] = block.timestamp + state.withdrawTimeLock;
emit RequestWithdraw(msg.sender, primaryAmount, secondaryAmount, state.withdrawExecutionTimestamp[msg.sender]);
}
影响: 即使有适当的配额,也无法代表其他用户发起提款,可能会导致资金滞留。
缓解措施: 将所有 msg.sender
替换为 from
参数。
214. FundingRateArbitrage 份额膨胀攻击 (JOJO)
模式: 经典的 ERC4626 风格的通过捐赠进行的膨胀攻击,允许窃取后续存款。
有漏洞的代码示例 (JOJO):
function getIndex() public view returns (uint256) {
if (totalEarnUSDCBalance == 0) {
return 1e18;
} else {
return SignedDecimalMath.decimalDiv(getNetValue(), totalEarnUSDCBalance);
}
}
function deposit(uint256 amount) external {
// ...
uint256 earnUSDCAmount = amount.decimalDiv(getIndex());
// 如果 index 膨胀,earnUSDCAmount 舍入为 0
}
攻击:
- 存款 1 份额
- 捐赠 100,000e6 USDC
- Index 变为 100,000e18
- 低于 100,000e6 的后续存款收到 0 份额
影响: 完全窃取后续用户存款。
缓解措施: 实施 OpenZeppelin 推荐的虚拟偏移。
215. 持票人资产转移漏洞 (Astaria)
模式: 留置权 token 充当持票人资产,允许恶意贷款人通过转移到黑名单地址来阻止贷款偿还。
有漏洞的代码示例 (Astaria):
function _getPayee(LienStorage storage s, uint256 lienId) internal view returns (address) {
return s.lienMeta[lienId].payee != address(0) ? s.lienMeta[lienId].payee : ownerOf(lienId);
}
// 发送到留置权 token 所有者的付款
function _payment(LienStorage storage s, Stack[] memory stack, ...) {
s.TRANSFER_PROXY.tokenTransferFrom(stack.lien.token, payer, payee, amount);
}
攻击场景:
- 贷款人提供 USDT/USDC 贷款
- 将留置权 token 转移到 USDC 黑名单地址
- 借款人无法偿还,贷款进入清算
- 由于黑名单,所有拍卖出价均失败
- 清算人免费申领抵押品
影响: 借款人损失抵押品,其他贷款人损失资金。
缓解措施: 基于拉取的支付系统或 token 允许列表。
216. 清算所任意结算 (Astaria)
模式: 任何人都可以使用任意参数调用 ClearingHouse.safeTransferFrom,从而允许抵押品盗窃。
有漏洞的代码示例 (Astaria):
function safeTransferFrom(address from, address to, uint256 identifier, uint256 amount, bytes calldata data) {
// 没有验证是否已进行拍卖
address paymentToken = bytes32(identifier).fromLast20Bytes();
_execute(from, to, paymentToken, amount);
// 删除所有留置权并销毁抵押品 token!
}
影响: 任何人都可以以零付款擦除抵押品状态。
缓解措施: 在 settleAuction 验证中将 AND 更改为 OR:
if (s.collateralIdToAuction[collateralId] == bytes32(0) ||
ERC721(s.idToUnderlying[collateralId].tokenContract).ownerOf(...) != s.clearingHouse[collateralId]) {
revert InvalidCollateralState(InvalidCollateralStates.NO_AUCTION);
}
217. Vault 策略绕过 (Astaria)
模式: 借款人可以绕过 vault 的验证,并在没有适当授权的情况下获得贷款。
有漏洞的代码示例 (Astaria):
function _validateCommitment(IAstariaRouter.Commitment calldata params, address receiver) internal view {
if (msg.sender != holder && receiver != holder && receiver != operator && !CT.isApprovedForAll(holder, msg.sender)) {
revert InvalidRequest(InvalidRequestReason.NO_AUTHORITY);
}
// 如果 receiver == holder,则通过,即使 msg.sender 未经授权!
}
攻击: 攻击者将 receiver 设置为抵押品所有者地址,绕过授权。
影响: 未经授权的贷款抵押任何抵押品。
缓解措施: 分开检查 msg.sender 和 receiver 的授权。
218. 缺少策略截止日期验证 (Astaria)
模式: VaultImplementation 不检查策略截止日期,允许过期的策略。
有漏洞的代码示例 (Astaria):
function _validateCommitment(IAstariaRouter.Commitment calldata params, address receiver) internal view {
// 缺失: if (block.timestamp > params.lienRequest.strategy.deadline) revert Expired();
// 仅验证签名,不验证截止日期
}
影响: 借款人可以使用条款可能不利的过时策略。
缓解措施: 在 vault 承诺验证中添加截止日期验证。
219. 清算初始询价溢出 (Astaria)
模式: liquidationInitialAsk > 2^88-1 导致清算回复,永久锁定抵押品。
有漏洞的代码示例 (Astaria):
auctionData.startAmount = stack[0].lien.details.liquidationInitialAsk.safeCastTo88();
// 如果 liquidationInitialAsk > type(uint88).max,则回复
影响: 抵押品永久锁定,无法清算。
缓解措施: 使用 uint256 作为拍卖 startAmount。
220. 策略师费用溢出攻击 (Astaria)
模式: 极高的 vault 费用会导致偿还回复。
有漏洞的代码示例 (Astaria):
uint88 feeInShares = convertToShares(fee).safeCastTo88();
// 如果费用转换为 shares > uint88 max,则回复
攻击: 策略师将费用设置为 1e13,导致溢出。
影响: 借款人无法偿还,强制清算。
缓解措施: 在创建时验证 vault 费用在合理范围内。
221. 通过 liquidationInitialAsk 进行的清算 DOS (Astaria)
模式: 借款人可以通过设置较低的 liquidationInitialAsk 来阻止未来的借款。
有漏洞的代码示例 (Astaria):
for (uint256 i = stack.length; i > 0; ) {
potentialDebt += _getOwed(newStack[j], newStack[j].point.end);
if (potentialDebt > newStack[j].lien.details.liquidationInitialAsk) {
revert InvalidState(InvalidStates.INITIAL_ASK_EXCEEDED);
}
}
攻击: 将 liquidationInitialAsk 设置为等于贷款金额,阻止所有未来的贷款。
影响: 借款人 DOS 无法获得额外贷款。
缓解措施: 仅根据位置 0 liquidationInitialAsk 检查总 stack 债务。
222. 公共 Vault 斜率损坏 (Astaria)
模式: 收购不会增加目标 vault 的斜率。
有漏洞的代码示例 (Astaria):
function buyoutLien(Stack[] calldata stack, uint8 position, ...) {
// 销毁旧的留置权,减少旧的 vault 斜率
if (_isPublicVault(s, payee)) {
IPublicVault(payee).handleBuyoutLien(...);
}
// 创建新的留置权,但不增加新的 vault 斜率!
}
影响: LPs 失去利息收入,借款人不支付利息。
缓解措施: 在收购中创建新的留置权时增加斜率。
223. 清算后的抵押品回收 (Astaria)
模式: 清算的抵押品可用于获得新的贷款。
有漏洞的代码示例 (Astaria):
function liquidatorNFTClaim(OrderParameters memory params) {
// 转移 NFT 但不结算拍卖
ERC721(token).safeTransferFrom(address(this), liquidator, tokenId);
// CollateralToken 仍然存在,可用于新的贷款!
}
影响: 无需实际抵押品即可获得贷款,从而耗尽 vault。
缓解措施: 在 liquidatorNFTClaim 中结算拍卖。
224. 留置权转移到公共 Vault 绕过 (Astaria)
模式: 攻击者可以将留置权转移到未创建的公共 vault 地址。
有漏洞的代码示例 (Astaria):
function transferFrom(address from, address to, uint256 id) {
if (_isPublicVault(s, to)) {
revert InvalidState(InvalidStates.PUBLIC_VAULT_RECIPIENT);
}
// 但是 vault 可能尚未存在!
}
攻击: 在创建之前将留置权转移到预测的公共 vault 地址。
影响: 借款人无法偿还,强制清算。
缓解措施: 为 vault 转移添加 require(to.code.length > 0)
。
225. 提取储备计算错误 (Astaria)
模式: 当 totalAssets <= expected 时,processEpoch 不正确地将 withdrawReserve 设置为 0。
有漏洞的代码示例 (Astaria):
if (totalAssets() > expected) {
s.withdrawReserve = (totalAssets() - expected).mulWadDown(s.liquidationWithdrawRatio).safeCastTo88();
} else {
s.withdrawReserve = 0; // 错误!仍然应该发送比例金额
}
影响: 尽管有索赔,WithdrawProxy 仍未收到资金。
缓解措施: 始终计算比例提取储备。
226. processEpoch 中的 Y 轴截距下溢 (Astaria)
模式: 大规模提款导致 y 轴截距计算下溢。
有漏洞的代码示例 (Astaria):
_setYIntercept(s, s.yIntercept - totalAssets().mulDivDown(s.liquidationWithdrawRatio, 1e18));
// 如果大多数用户提取,则下溢
影响: Epoch 处理失败,用户无法提取。
缓解措施: 在 processEpoch 之前调用 accrue() 以更新 y 轴截距。
227. 清算定价中的小数不匹配 (Astaria)
模式: 非 18 位小数资产会导致不正确的拍卖起始价格。
有漏洞的代码示例 (Astaria):
startingPrice: stack[0].lien.details.liquidationInitialAsk, // 假设 18 位小数
settlementToken: stack[position].lien.token, // 可能是 6 位小数 (USDC)
影响: 对于 USDC,拍卖以 1e12x 的预期价格开始。
缓解措施: 按 token 小数缩放 liquidationInitialAsk。
228. 针对清算的再融资攻击 (Astaria)
模式: 恶意再融资就在低 initialAsk 清算之前进行。
攻击场景:
- 用户获得 3 以太币贷款,其中 100 以太币 liquidationInitialAsk
- 在清算之前,攻击者以 0.1 以太币 initialAsk 进行再融资
- NFT 以远低于公平价值的价格清算
影响: 由于操纵的拍卖价格,借款人损失 NFT 价值。
缓解措施: 阻止再融资接近清算或保持最小的 initialAsk。
229. 承诺重放攻击 (Astaria)
模式: 相同的承诺签名可以多次用于获得额外的贷款。
有漏洞的代码示例 (Astaria):
function _validateCommitment(IAstariaRouter.Commitment calldata params, address receiver) internal view {
// 仅验证签名,不跟踪是否已使用承诺
address recovered = ecrecover(
keccak256(_encodeStrategyData(s, params.lienRequest.strategy, params.lienRequest.merkle.root)),
params.lienRequest.v,
params.lienRequest.r,
params.lienRequest.s
);
}
攻击过程:
- 用户通过具有有效承诺的 Router 获得贷款
- 用户手动将 NFT 转移到 CollateralToken
- 使用相同的承诺直接在 vault 上调用 commitToLien
- 将 strategy.vault 更改为生成新的 lienId
- 将之前的交易添加到 stack 以通过验证
影响: 使用单个批准的承诺的多个贷款。
缓解措施: 添加随机数nonce系统以跟踪已使用的承诺。
230. 收购留置权清算询价验证错误 (Astaria)
模式: 收购验证使用旧的 stack 而不是替换后的新的 stack。
有漏洞的代码示例 (Astaria):
function _buyoutLien(LienStorage storage s, ILienToken.LienActionBuyout calldata params) internal {
// ... 收购逻辑 ...
for (uint256 i = params.encumber.stack.length; i > 0; ) {
potentialDebt += _getOwed(params.encumber.stack[j], params.encumber.stack[j].point.end);
if (potentialDebt > params.encumber.stack[j].lien.details.liquidationInitialAsk) { // 使用旧的 stack!
revert InvalidState(InvalidStates.INITIAL_ASK_EXCEEDED);
}
}
// ... 替换为新的留置权 ...
newStack = _replaceStackAtPositionWithNewLien(s, params.encumber.stack, params.position, newLien, ...);
}
影响: 收购可能通过,但初始清算询问不足以支付总债务。
缓解措施: 针对替换后的 newStack 进行验证。
231. 结算检查逻辑错误 (Astaria)
模式: AND 条件应为 settleAuction 验证中的 OR。
有漏洞的代码示例 (Astaria):
function settleAuction(uint256 collateralId) public {
if (
s.collateralIdToAuction[collateralId] == bytes32(0) &&
ERC721(s.idToUnderlying[collateralId].tokenContract).ownerOf(
s.idToUnderlying[collateralId].tokenId
) != s.clearingHouse[collateralId]
) {
revert InvalidCollateralState(InvalidCollateralStates.NO_AUCTION);
}
}
影响: ClearingHouse.safeTransferFrom 即使没有拍卖也可以执行。
缓解措施: 将 AND 更改为 OR 条件。
232. Router 批准要求不匹配 (Astaria)
模式: commitToLiens 需要批准所有 NFT,而不是批准各个 NFT。
有漏洞的代码示例 (Astaria):
function _validateCommitment(IAstariaRouter.Commitment calldata params, address receiver) internal view {
if (
msg.sender != holder &&
receiver != holder &&
receiver != operator && // 应该检查 msg.sender == operator
!CT.isApprovedForAll(holder, msg.sender)
) {
revert InvalidRequest(InvalidRequestReason.NO_AUTHORITY);
}
}
影响: 标准 NFT 批准工作流程不起作用,迫使用户批准整个集合。
缓解措施: 更改为 msg.sender != operator
。
233. 自我清算激励利用 (Astaria)
模式: 借款人可以清算自己以获得清算费用。
有漏洞的代码示例 (Astaria):
function canLiquidate(ILienToken.Stack memory stack) public view returns (bool) {
return (stack.point.end <= block.timestamp ||
msg.sender == s.COLLATERAL_TOKEN.ownerOf(stack.lien.collateralId));
}
攻击场景:
- 借款人获得 10 WETH 贷款
- 在贷款到期之前,借款人调用 liquidate()
- 借款人被设置为清算人,获得 13% 的清算费
- 保留原始的 10 WETH 加上 1.3 WETH 奖金
影响: 贷款人遭受损失,而借款人从违约中获利。
缓解措施: 在贷款公开可清算之前,阻止自我清算。
234. 私人 Vault ERC777 恶意破坏 (Astaria)
模式: 私人 vault 所有者可以通过 ERC777 回调拒绝贷款偿还。
有漏洞的代码示例 (Astaria):
function _payment(LienStorage storage s, Stack[] memory stack, ...) internal {
// 对于私人 vault,付款到所有者那里
address payee = _getPayee(s, lienId);
s.TRANSFER_PROXY.tokenTransferFrom(stack.lien.token, payer, payee, amount);
}
function recipient() public view returns (address) {
if (IMPL_TYPE() == uint8(IAstariaRouter.ImplementationType.PublicVault)) {
return address(this);
} else {
return owner(); // 私人```markdown
// 但是 Vault (私有的) 没有
function deposit(uint256 amount, address receiver)
public
virtual
returns (uint256) { // 没有 whenNotPaused!
VIData storage s = _loadVISlot();
require(s.allowList[msg.sender] && receiver == owner());
ERC20(asset()).safeTransferFrom(msg.sender, address(this), amount);
return amount;
}
影响:即使协议暂停/关闭,私有 vault 也可以接收存款。
缓解措施:将 whenNotPaused 修饰符添加到私有 vault 存款。
237. 策略师利息奖励计算错误 (Astaria)
模式:策略师奖励基于全部贷款金额计算,而不是支付金额。
有漏洞的代码示例 (Astaria):
function _payment(LienStorage storage s, Stack[] memory activeStack, ...) internal {
if (isPublicVault) {
IPublicVault(lienOwner).beforePayment(
IPublicVault.BeforePaymentParams({
interestOwed: owed - stack.point.amount,
amount: stack.point.amount, // 应该是支付金额!
lienSlope: calculateSlope(stack)
})
);
}
}
影响:即使在最小支付的情况下,策略师也能获得过多的奖励。
缓解措施:传递实际支付金额,而不是 stack.point.amount。
238. Fee-on-Transfer 代币锁定 (Astaria)
模式:PublicVault 账户在 fee-on-transfer 代币上会中断。
有漏洞的代码示例 (Astaria):
function deposit(uint256 assets, address receiver) public virtual returns (uint256 shares) {
ERC20(asset()).safeTransferFrom(msg.sender, address(this), assets);
_mint(receiver, shares);
// 如果代币收取费用,实际收到的 < assets
// 但是 shares 是基于 assets 铸造的
}
影响:后来的提款人无法赎回,因为 vault 的 assets 少于预期。
缓解措施:计算实际收到的金额或将代币列入白名单。
239. 部分支付的利息复利 (Astaria)
模式:每次部分支付都会将利息添加到本金,从而增加债务。
有漏洞的代码示例 (Astaria):
function _payment(LienStorage storage s, Stack[] memory activeStack, ...) internal {
uint256 owed = _getOwed(stack, block.timestamp); // 本金 + 利息
stack.point.amount = owed.safeCastTo88(); // 更新本金以包括利息
stack.point.last = block.timestamp.safeCastTo40();
if (stack.point.amount > amount) {
stack.point.amount -= amount.safeCastTo88(); // 剩余的包括复利
}
}
影响:尽管声称是单利,但协议收取复利。
缓解措施:将应计利息与本金分开跟踪。
240. 拍卖窗口期间的清算 (Astaria)
模式:对手可以使用荷兰式拍卖以最小的支付来结算拍卖。
有漏洞的代码示例 (Astaria):
function _generateValidOrderParameters(...) internal returns (OrderParameters memory) {
considerationItems[0] = ConsiderationItem(
ItemType.ERC20,
settlementToken,
uint256(0),
prices[0], // 起始价格
prices[1], // 结束价格:1000 wei
payable(address(s.clearingHouse[collateralId]))
);
}
攻击:
- 荷兰式拍卖价格降至 1000 wei
- 攻击者将 1001 wei 转账到 ClearingHouse
- 调用 safeTransferFrom 来结算拍卖
- NFT 由清算人声明,贷款人损失
缓解措施:验证拍卖完成和 NFT 转账。
241. Public Vault yIntercept 溢出 (Astaria)
模式:未检查的加法可能导致 yIntercept 溢出。
有漏洞的代码示例 (Astaria):
function _increaseYIntercept(VaultData storage s, uint256 amount) internal {
s.yIntercept += amount.safeCastTo88(); // 只有 88 位
}
影响:totalAssets 计算中断,阻止提款。
缓解措施:使用检查的算术或更大的存储类型。
242. Public Vault Slope 溢出 (Astaria)
模式:高利率导致 slope 溢出。
有漏洞的代码示例 (Astaria):
function afterPayment(uint256 computedSlope) public onlyLienToken {
s.slope += computedSlope.safeCastTo48(); // 多个贷款可能溢出
}
影响:不正确的 totalAssets 计算,中断的 vault 账户。
缓解措施:删除未检查的块,考虑更大的存储。
243. 最小存款计算错误 (Astaria)
模式:非 18 位小数的代币具有过多的最小存款。
有漏洞的代码示例 (Astaria):
function minDepositAmount() public view returns (uint256) {
if (ERC20(asset()).decimals() == uint8(18)) {
return 100 gwei;
} else {
return 10**(ERC20(asset()).decimals() - 1); // 0.1 个代币,无论价值如何
}
}
影响:WBTC 最小存款 > $2000,排除许多用户。
缓解措施:根据小数位数缩放最小值:
if (decimals < 4) return 10**(decimals - 1);
else if (decimals < 8) return 10**(decimals - 2);
else return 10**(decimals - 6);
244. 提取储备金中的 ProcessEpoch 溢出 (Astaria)
模式:未检查的乘法可能因任意代币而溢出。
有漏洞的代码示例 (Astaria):
unchecked {
if (totalAssets() > expected) {
s.withdrawReserve = (totalAssets() - expected)
.mulWadDown(s.liquidationWithdrawRatio)
.safeCastTo88();
}
}
影响:ProcessEpoch 失败,阻止提款。
缓解措施:删除未检查的块或验证 totalAssets 的大小。
245. WithdrawProxy 提前赎回 (Astaria)
模式:用户可以在 vault 转账提取储备金之前赎回。
有漏洞的代码示例 (Astaria):
modifier onlyWhenNoActiveAuction() {
if (s.finalAuctionEnd != 0) { // 拍卖开始前为 0
revert InvalidState(InvalidStates.NOT_CLAIMED);
}
_;
}
function redeem(uint256 shares, address receiver, address owner) public onlyWhenNoActiveAuction {
if (totalAssets() > 0) { // 可以通过少量捐赠来满足
// 允许赎回
}
}
攻击:向 WithdrawProxy 存入少量金额,从而实现提前的不公平赎回。
影响:提前赎回者获得的份额少于公平份额。
缓解措施:为提款安全时添加显式标志。
246. Lien Position 删除失败 (Astaria)
模式:使用 memory 而不是 storage 阻止 lien 删除。
有漏洞的代码示例 (Astaria):
function _paymentAH(
LienStorage storage s,
address token,
AuctionStack[] memory stack, // 应该是 storage!
uint256 position,
uint256 payment,
address payer
) internal returns (uint256) {
delete s.lienMeta[lienId]; // 有效
delete stack[position]; // 对 storage 没有影响!
}
影响:Ghost liens 在还款后仍保留在 storage 中。
缓解措施:将参数更改为 storage 或单独处理删除。
247. 策略师收购恶意行为 (Astaria)
模式:策略师可以通过重复收购 lien 来阻止提款。
攻击场景:
- LPs 为 epoch 发出提款信号
- 策略师抢先 transferWithdrawReserve
- 调用 buyoutLien 以消耗可用资金
- WithdrawProxy 保持未注资状态
- 无限期重复
影响:LPs 被永久锁定,无法提款。
缓解措施:在 buyout 之前强制执行 transferWithdrawReserve,如 commitToLien。
248. 非 18 位小数的代币支持问题 (Astaria)
模式:使用非 18 位小数代币的多个计算错误。
问题:
- PublicVault 小数位数硬编码为 18
- liquidationWithdrawRatio 计算假设 18 位小数
- WithdrawProxy 计算使用 1e18 常量
- 对于低小数位数代币,minDepositAmount 过多
影响:vault 无法使用或与 USDC、WBTC 等中断。
缓解措施:使小数位数与整个底层代币匹配。
249. ProcessEpoch YIntercept 下溢 (Astaria)
模式:高 liquidationWithdrawRatio 导致下溢。
有漏洞的代码示例 (Astaria):
_setYIntercept(
s,
s.yIntercept - totalAssets().mulDivDown(s.liquidationWithdrawRatio, 1e18)
);
影响:ProcessEpoch 恢复,阻止 epoch 转换。
缓解措施:在 processEpoch 之前调用 accrue() 以更新 yIntercept。
250. 清算账户缺口 (Astaria)
模式:缺少 updateVaultAfterLiquidation 阻止正确的 WithdrawProxy 设置。
有漏洞的代码示例 (Astaria):
// 清算可能在没有通知 vault 的情况下发生
// 如果 withdrawProxy 未部署,它永远不会获得拍卖资金
影响:如果 epoch 边界临近,LPs 不会收到清算收益。
缓解措施:确保始终调用 updateVaultAfterLiquidation。
251. GMX 冷静期赎回阻止 (RedactedCartel)
模式:如果冷静期内发生任何存款,GMX 在 GlpManager 上的 cooldownDuration 会阻止赎回。
有漏洞的代码示例:
// GMX GlpManager 强制执行冷静期
function _removeLiquidity(...) {
require(lastAddedAt[account] + cooldownDuration <= block.timestamp);
}
攻击场景:
- 自然阻止:如果 10% 的 GMX 用户使用 Pirex,则 95% 的时间赎回失败
- 恶意攻击:每 15 分钟存入 1 wei 以永久阻止赎回(成本:约 3.5k 美元/年)
- GMX 参数更改:将 cooldownDuration 增加到 2 天会中断赎回
影响:用户无法从 PirexGmx 提取资金。
缓解措施:为仅赎回期间保留特定时间范围。
252. 动态排放率奖励计算错误 (RedactedCartel)
模式:奖励分配假设恒定的排放率,但 GMX 使用动态率。
有漏洞的代码示例:
// 线性计算奖励
uint256 rewards = globalState.rewards +
(block.timestamp - lastUpdate) *
lastSupply;
真实场景:
- Alice 在排放率为 2 esGMX/秒时进行抵押
- Bob 在排放率降至 1 esGMX/秒时进行抵押
- 由于线性计算,Alice 收到的奖励少于应得的奖励
影响:一些用户失去奖励,而另一些用户则获得额外奖励。
缓解措施:实施 RewardPerToken 模式以处理动态率。
253. 零份额提款攻击 (RedactedCartel)
模式:份额计算中的向下舍入允许免费提取 assets。
有漏洞的代码示例:
function withdraw(uint256 assets, address receiver, address owner) public {
shares = previewWithdraw(assets); // 可以向下舍入为 0
_burn(owner, shares); // 燃烧 0 份额
asset.safeTransfer(receiver, assets); // 转移 assets
}
攻击:总 assets 为 1000 WETH,总供应量为 10 份额,提取 99 WETH 会舍入为 0 份额。
缓解措施:在 previewWithdraw 中使用向上舍入:
uint256 shares = supply == 0 ? assets : assets.mulDivUp(supply, totalAssets());
254. 小头寸奖励损失 (RedactedCartel)
模式:由于舍入,具有小头寸的用户会损失所有奖励。
有漏洞的代码示例:
uint256 amount = (rewardState * userRewards) / globalRewards;
// 如果 userRewards << globalRewards,则 amount 舍入为 0
p.userStates[user].rewards = 0; // 但奖励无论如何都会被清除
影响:小额存款人永久失去奖励;恶意用户可以通过为受害者调用 claim 来进行恶意破坏。
缓解措施:
- 实施 RewardPerToken 方法
- 如果计算的奖励为 0,则恢复
255. 通过 vGMX/vGLP 阻止 GMX 迁移 (RedactedCartel)
模式:不可转让的 vester 代币的直接转账会阻止迁移。
攻击:将 vGMX 或 vGLP 代币直接发送到 PirexGmx 以永久阻止:
function signalTransfer(address _receiver) external {
require(IERC20(gmxVester).balanceOf(msg.sender) == 0);
require(IERC20(glpVester).balanceOf(msg.sender) == 0);
}
影响:协议迁移变得不可能。
注意:Vester 代币会覆盖转账方法以恢复,从而将攻击限制在 GMX 内部人员。
256. 复合函数操作 (RedactedCartel)
模式:具有用户控制的交换参数的公共复合函数启用 MEV。
有漏洞的代码示例:
function compound(
uint24 fee, // 用户控制池选择
uint256 amountOutMinimum, // 可以设置为 1
uint160 sqrtPriceLimitX96,
bool optOutIncentive
) public {
gmxAmountOut = SWAP_ROUTER.exactInputSingle({
fee: fee, // 攻击者选择流动性不足的池
amountOutMinimum: amountOutMinimum, // 接受高滑点
});
}
攻击:通过流动性不足的池进行交换,并进行夹击获利。
缓解措施:使用 poolFee 参数和链上 oracle 来获取最小金额。
257. ERC4626 MaxWithdraw 实施错误 (RedactedCartel)
模式:MaxWithdraw 没有考虑提款罚款。
有漏洞的代码:
// 在 PirexERC4626 中(由 AutoPxGmx/AutoPxGlp 继承)
function maxWithdraw(address owner) public view returns (uint256) {
return convertToAssets(balanceOf(owner)); // 忽略罚款
}
影响:使用 maxWithdraw 金额调用 withdraw 始终恢复。
缓解措施:在 AutoPxGmx/AutoPxGlp 中覆盖:
function maxWithdraw(address owner) public view override returns (uint256) {
uint256 assets = convertToAssets(balanceOf(owner));
uint256 penalty = ... // 计算罚款
return assets - penalty;
}
258. 平台更新批准损失 (RedactedCartel)
模式:更新平台地址不会向新平台授予批准。
有漏洞的代码:
function setPlatform(address _platform) external onlyOwner {
platform = _platform; // 未授予批准
}
影响:平台更新后存款失败。
缓解措施:
function setPlatform(address _platform) external onlyOwner {
gmx.safeApprove(platform, 0);
gmx.safeApprove(_platform, type(uint256).max);
platform = _platform;
}
259. Fee-on-Transfer 代币不兼容 (RedactedCartel)
模式:depositGlp 假设收到的金额等于发送的金额。
有漏洞的代码:
t.safeTransferFrom(msg.sender, address(this), tokenAmount);
deposited = gmxRewardRouterV2.mintAndStakeGlp(
token,
tokenAmount, // 假设收到全部金额
minUsdg,
minGlp
);
影响:交易会恢复 FOT 代币,如 USDT。
缓解措施:
uint256 balanceBefore = t.balanceOf(address(this));
t.safeTransferFrom(msg.sender, address(this), tokenAmount);
uint256 actualAmount = t.balanceOf(address(this)) - balanceBefore;
260. 直接奖励索赔绕行 (RedactedCartel)
模式:任何人都可以直接索赔奖励,绕过复合逻辑和费用。
攻击流程:
- 直接调用
PirexRewards.claim(pxGmx, AutoPxGlp)
- 奖励发送到 vault,无需执行 compound()
- 不会计算或收取费用
- pxGmx 奖励未在 _harvest() 中跟踪
影响:
- 协议失去费用收入
- 用户失去 pxGmx 奖励
缓解措施:跟踪之前的余额并检测直接转账。
261. 零 TotalSupply 除法 (RedactedCartel)
模式:当 RewardTracker totalSupply 为 0 时,_calculateRewards 会恢复。
有漏洞的代码:
uint256 cumulativeRewardPerToken = r.cumulativeRewardPerToken() +
((blockReward * precision) / r.totalSupply()); // 除以零
影响:当任何 RewardTracker 为空时,harvest() 和 claim() 变得无法使用。
缓解措施:在除法之前检查 totalSupply:
if (r.totalSupply() == 0) return 0;
262. 奖励代币管理不善 (RedactedCartel)
模式:PirexGmx 中硬编码的奖励与 PirexRewards 中可配置的奖励不匹配。
问题:所有者可以:
- 删除硬编码的奖励代币
- 添加不支持的代币
- 清除奖励数组
影响:当代币配置错误但状态仍然清除时,用户会失去奖励。
263. 硬编码的兑换路由 (RedactedCartel)
模式:SWAP_ROUTER 地址硬编码为 Arbitrum,与 Avalanche 不兼容。
有漏洞的代码:
IV3SwapRouter public constant SWAP_ROUTER =
IV3SwapRouter(0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45);
影响:AutoPxGmx 在 Avalanche 上完全中断。
缓解措施:在构造函数中传递路由器地址。
264. GlpManager 在动荡期间的滑点 (RedactedCartel)
模式:Compound 接受用户控制的 minUsdg/minGlp 参数。
风险:在价格波动或 oracle 操纵期间,compound 值可能会丢失。
缓解措施:使用 oracle 计算最小金额,而不是用户输入。
265. 迁移奖励损失窗口 (RedactedCartel)
模式:在 completeMigration 和 PirexRewards 生产者更新之间,奖励会丢失。
有漏洞的流程:
- 调用 completeMigration()
- AutoPxGmx.compound() → 旧的 PirexGmx.claimRewards()
- 返回零奖励,损失用户资金
缓解措施:在 migrateReward() 中设置 pirexRewards = address(0)。
266. ConfigureGmxState 批准累积 (RedactedCartel)
模式:不重置旧的 stakedGmx 批准,导致多个问题。
问题:
- 如果 stakedGmx 未更改,则无法第二次调用该函数
- 旧合约保留支出批准
- 无法恢复到之前的 stakedGmx 地址
缓解措施:在设置新的批准之前重置旧的批准:
gmx.safeApprove(address(stakedGmx), 0);
gmx.safeApprove(address(newStakedGmx), type(uint256).max);
267. ERC4626 合规性违规 (RedactedCartel)
模式:maxDeposit/maxMint 返回 type(uint256).max,而不管实际限制如何。
问题:违反了 EIP-4626 不高估容量的要求。
缓解措施:
function maxMint(address) public view virtual returns (uint256) {
if (totalSupply >= maxSupply) return 0;
return maxSupply - totalSupply;
}
268. 汇率操纵预防 (PoolTogether H-01)
模式:汇率计算错误地限制了可提取的 assets,从而阻止了汇率的增加。
有漏洞的代码示例 (PoolTogether):
function _currentExchangeRate() internal view returns (uint256) {
uint256 _withdrawableAssets = _yieldVault.maxWithdraw(address(this));
if (_withdrawableAssets > _totalSupplyToAssets) {
_withdrawableAssets = _withdrawableAssets - (_withdrawableAssets - _totalSupplyToAssets);
}
return _withdrawableAssets.mulDiv(_assetUnit, _totalSupplyAmount, Math.Rounding.Down);
}
影响:Vault 无法从抵押不足中恢复;汇率永久卡住。
缓解措施:删除对可提取 assets 的人为限制。
269. 提款中的类型转换溢出 (PoolTogether H-02)
模式:燃烧份额时从 uint256 到 uint96 的静默向下转换。
有漏洞的代码示例 (PoolTogether):
function _burn(address _owner, uint256 _shares) internal virtual override {
_twabController.burn(msg.sender, _owner, uint96(_shares)); // 静默向下转换!
}
影响:用户可以提取全部 assets,同时仅燃烧 uint96 价值的份额,从而耗尽 vault。
缓解措施:为所有类型转换使用 SafeCast 库。
270. Assets/份额类型混淆 (PoolTogether H-03)
模式:函数参数可以互换地用作 assets 和份额,而无需转换。
有漏洞的代码示例 (PoolTogether):
function liquidate(address _account, address _tokenIn, uint256 _amountIn, address _tokenOut, uint256 _amountOut) public {
if (_amountOut > _liquidatableYield) revert LiquidationAmountOutGTYield(_amountOut, _liquidatableYield);
_increaseYieldFeeBalance(
(_amountOut * FEE_PRECISION) / (FEE_PRECISION - _yieldFeePercentage) - _amountOut
);
_mint(_account, _amountOut);
}
影响:由于混合了 asset 和份额金额,清算逻辑完全中断。
缓解措施:使用正确的转换清楚地分隔 asset 和份额金额。
271. 收益费用铸造访问控制 (PoolTogether H-04)
模式:任何人都可以将收益费用铸造给任何接收者。
有漏洞的代码示例 (PoolTogether):
function mintYieldFee(uint256 _shares, address _recipient) external {
_requireVaultCollateralized();
if (_shares > _yieldFeeTotalSupply) revert YieldFeeGTAvailable(_shares, _yieldFeeTotalSupply);
_yieldFeeTotalSupply -= _shares;
_mint(_recipient, _shares); // 任何人都可以铸造到任何地址!
emit MintYieldFee(msg.sender, _recipient, _shares);
}
影响:完全盗窃协议收益费用。
缓解措施:删除接收者参数;仅铸造给指定的收益费用接收者。
272. 强制删除委派 (PoolTogether H-05)
模式:sponsor() 函数可以强制删除用户委派。
有漏洞的代码示例 (PoolTogether):
function sponsor(uint256 _amount, address _receiver) external {
_deposit(msg.sender, _receiver, _amount, _amount);
if (_twabController.delegateOf(address(this), _receiver) != SPONSORSHIP_ADDRESS) {
_twabController.delegate(address(this), _receiver, SPONSORSHIP_ADDRESS);
}
}
影响:攻击者可以通过赞助 0 金额来删除所有委派,从而操纵彩票几率。
缓解措施:仅当接收者已委派给赞助地址时才强制委派。
273. 委派到零地址 (PoolTogether H-06)
模式:委派到 address(0) 会永久锁定资金。
有漏洞的代码示例 (PoolTogether):
function _delegate(address _vault, address _from, address _to) internal {
address _currentDelegate = _delegateOf(_vault, _from);
delegates[_vault][_from] = _to;
_transferDelegateBalance(
_vault,
_currentDelegate,
_to, // 如果 _to 是 address(0),则资金丢失!
uint96(userObservations[_vault][_from].details.balance)
);
}
影响:用户在尝试重置委派时会失去所有资金。
缓解措施:阻止委派到 address(0)。
274. 抵押检查时序 (PoolTogether H-07)
模式:在函数开始时而不是结束时检查抵押。
有漏洞的代码示例 (PoolTogether):
function mintYieldFee(uint256 _shares, address _recipient) external {
_requireVaultCollateralized(); // 开始时检查
_yieldFeeTotalSupply -= _shares;
_mint(_recipient, _shares);
// 现在 vault 可能抵押不足!
}
影响:操作可能会使 vault 抵押不足。
缓解措施:将抵押检查移到更改状态函数的末尾。
275. 储备账户绕行 (PoolTogether H-08)
模式:直接储备增加不会更新已入账的余额。
有漏洞的代码示例 (PoolTogether):
function increaseReserve(uint104 _amount) external {
_reserve += _amount;
prizeToken.safeTransferFrom(msg.sender, address(this), _amount);
// accountedBalance 未更新!
}
function contributePrizeTokens(address _prizeVault, uint256 _amount) external {
uint256 _deltaBalance = prizeToken.balanceOf(address(this)) - _accountedBalance();
// 可以窃取储备注入!
}
影响:Vault 可以窃取储备捐款,重复计算奖品代币。
缓解措施:在已入账的余额计算中跟踪储备注入。
276. ERC4626 Vault 兼容性 (PoolTogether H-09)
模式:使用 maxWithdraw 获取汇率可能会导致某些 vault 类型的损失。
有漏洞的代码示例 (PoolTogether):
function _currentExchangeRate() internal view returns (uint256) {
uint256 _withdrawableAssets = _yieldVault.maxWithdraw(address(this));
// 一些 vaults 返回少于实际余额!
}
影响:通过具有借贷机制或提款限制的 vaults 操纵汇率。
缓解措施:记录不兼容的 vault 类型或使用不同的计算方法。
277. 基于 Hook 的攻击媒介 (PoolTogether M-02)
模式:用户控制的 hooks 启用各种攻击媒介。
有漏洞的代码示例 (PoolTogether):
function setHooks(VaultHooks memory hooks) external {
_hooks[msg.sender] = hooks; // 无验证!
emit SetHooks(msg.sender, hooks);
}
function _claimPrize(...) internal returns (uint256) {
if (hooks.useBeforeClaimPrize) {
recipient = hooks.implementation.beforeClaimPrize(_winner, _tier, _prizeIndex);
// 可以恢复、操纵状态或恶意破坏!
}
}
影响:恶意破坏攻击、重入、未经授权的外部调用、DoS。
缓解措施:为 hook 调用添加 gas 限制和错误处理。
278. TWAB 时间范围安全性 (PoolTogether M-03)
模式:缺少历史余额查询的时间范围验证。
有漏洞的代码示例 (PoolTogether):
function _getVaultUserBalanceAndTotalSupplyTwab(address _vault, address _user, uint256 _drawDuration) internal view returns (uint256 twab, uint256 twabTotalSupply) {
uint32 _endTimestamp = uint32(_lastClosedDrawStartedAt + drawPeriodSeconds);
uint32 _startTimestamp = uint32(_endTimestamp - _drawDuration * drawPeriodSeconds);
twab = twabController.getTwabBetween(_vault, _user, _startTimestamp, _endTimestamp);
// 无 isTimeRangeSafe 检查!
}
影响:不准确的 TWAB 计算影响奖品分配。
缓解措施:在 getTwabBetween 调用之前添加 isTimeRangeSafe 验证。
279. 缺少最大铸造验证 (PoolTogether M-04)
模式:deposit() 不检查生成的份额是否超过 maxMint。
有漏洞的代码示例 (PoolTogether):
function deposit(uint256 _assets, address _receiver) public returns (uint256) {
if (_assets > maxDeposit(_receiver)) revert DepositMoreThanMax(_receiver, _assets, maxDeposit(_receiver));
// 不检查 shares > maxMint!
}
影响:可以使用抵押不足的 vaults 铸造超过协议限制的份额。
缓解措施:在 deposit 函数中添加 maxMint 验证。
280. 赞助地址余额不变式 (PoolTogether M-05)
模式:转移到 SPONSORSHIP_ADDRESS 会中断总供应量计算。
有漏洞的代码示例 (PoolTogether):
function _transferBalance(...) internal {
if (_to != address(0)) {
_increaseBalances(_vault, _to, _amount, _isToDelegate ? _amount : 0);
if (!_isToDelegate && _toDelegate != SPONSORSHIP_ADDRESS) {
_increaseBalances(_vault, _toDelegate, 0, _amount);
}
// SPONSORSHIP_ADDRESS 余额增加,但总数不增加!
}
}
影响:个人余额之和超过总供应量,从而歪曲了赔率。
缓解措施:禁止转移到 SPONSORSHIP_ADDRESS。
281. Draw Manager 抢先交易 (PoolTogether M-06)
模式:如果尚未设置 draw manager,则任何人都可以设置 draw manager。
有漏洞的代码示例 (PoolTogether):
function setDrawManager(address _drawManager) external {
if (drawManager != address(0)) {
revert DrawManagerAlreadySet();
}
drawManager = _drawManager; // 无访问控制!
emit DrawManagerSet(_drawManager);
}
影响:恶意 draw manager 可以提取储备金并操纵抽奖。
缓解措施:添加访问控制或仅在构造函数中设置。
282. 数学库 Pow() 不一致 (PoolTogether M-07)
模式:PRBMath pow() 函数返回不一致的值。
影响:不正确的等级赔率和抽奖累加器计算。
缓解措施:升级到 PRBMath v4 和 Solidity 0.8.19。
283. CREATE1 部署抢先交易 (PoolTogether M-08)
模式:Vault 部署容易受到抢先交易的影响。
有漏洞的代码示例 (PoolTogether):
function deployVault(...) external returns (address) {
vault = address(new Vault{salt: salt}(...)); // CREATE1 部署
}
影响:攻击者可以在同一地址部署恶意 vault。
缓解措施:使用 CREATE2,并将 vault 配置用作 salt。
284. 最小奖品激励上限 (PoolTogether M-09)
模式:申领者费用上限为最小奖金规模。
有漏洞的代码示例 (PoolTogether):
function _computeMaxFee(uint8 _tier, uint8 _numTiers) internal view returns (uint256) {
uint8 _canaryTier = _numTiers - 1;
if (_tier != _canaryTier) {
return _computeMaxFee(prizePool.getTierPrizeSize(_canaryTier - 1));
}
}
影响:当 gas 成本超过最小奖品时,没有动力申领大奖。
缓解措施:根据实际等级奖品规模设置最大费用。
285. 奖品大小向下转换溢出 (PoolTogether M-10)
模式:uint256 到 uint96 的不安全向下转换为奖品大小。影响:奖金分配与预期设计不符。
缓解措施:使用正确的算法重新计算层级的赔率。
288. 观察创建操纵 (PoolTogether M-13)
模式:用户可以阻止新观察的创建以操纵平均值。
存在漏洞的代码示例 (PoolTogether):
if (currentPeriod == 0 || currentPeriod > newestObservationPeriod) {
return (
uint16(RingBufferLib.wrap(_accountDetails.nextObservationIndex, MAX_CARDINALITY)),
newestObservation,
true
);
}
// 小额频繁存款保持周期相等,阻止新观察
影响:用户可以操纵他们的平均余额以进行抽奖。
缓解措施:使 TWAB 查询与周期边界对齐。
289. 带有单个 Canary Claim(金丝雀声明)的层级扩展 (PoolTogether M-14)
模式:一个canary claim导致层级计数增加。
存在漏洞的代码示例 (PoolTogether):
function _computeNextNumberOfTiers(uint8 _numTiers) internal view returns (uint8) {
uint8 _nextNumberOfTiers = largestTierClaimed + 2;
if (_nextNumberOfTiers >= _numTiers && /* threshold checks */) {
_nextNumberOfTiers = _numTiers + 1;
}
return _nextNumberOfTiers; // 总是返回增加的计数!
}
影响:快速的层级扩展稀释了奖金。
缓解措施:仅在满足阈值时扩展层级。
290. 层级维护 DoS (PoolTogether M-15)
模式:单个用户可以保持无利可图的层级处于活动状态。
攻击:以亏损的方式从最高层级申领一份奖金,以保持层级计数。
影响:阻止层级减少,使奖金的申领变得无利可图。
缓解措施:改进层级收缩逻辑。
291. 最大层级阈值绕过 (PoolTogether M-16)
模式:达到最大层级时跳过阈值检查。
存在漏洞的代码示例 (PoolTogether):
if (_nextNumberOfTiers >= MAXIMUM_NUMBER_OF_TIERS) {
return MAXIMUM_NUMBER_OF_TIERS; // 跳过阈值验证!
}
影响:在不满足声明阈值的情况下添加第 15 个层级。
缓解措施:始终在层级扩展之前验证阈值。
292. CREATE2 防抢跑 (PoolTogether M-08 缓解措施)
模式:使用具有确定性地址的 CREATE2 可以防止抢跑。
安全实现:
function deployVault(...) external returns (address vault) {
bytes32 salt = keccak256(abi.encode(_name, _symbol, _yieldVault, _prizePool, _claimer, _yieldFeeRecipient, _yieldFeePercentage, _owner));
vault = address(new Vault{salt: salt}(...));
}
影响:防止在预测的地址上进行恶意 vault 部署。
293. Vault 小数精度损失 (PoolTogether M-22)
模式:精度损失被视为 vault 损失。
存在漏洞的代码示例 (PoolTogether):
function _currentExchangeRate() internal view returns (uint256) {
uint256 _withdrawableAssets = _yieldVault.maxWithdraw(address(this));
// 1 wei 精度损失触发抵押不足模式!
}
影响:正常的精度损失会阻止存款。
缓解措施:为精度损失增加 1 wei 的容差。
294. ERC4626 视图函数合规性 (PoolTogether M-23)
模式:maxDeposit/maxMint 不检查 yield vault 限制。
存在漏洞的代码示例 (PoolTogether):
function maxDeposit(address) public view virtual override returns (uint256) {
return _isVaultCollateralized() ? type(uint96).max : 0;
// 忽略 _yieldVault.maxDeposit()!
}
影响:与期望 ERC4626 合规性的协议集成失败。
缓解措施:返回 vault 限制和 yield vault 限制的最小值。
295. Claimer 奖金申领抢跑 (PoolTogether M-24)
模式:机器人可以通过抢跑批处理中的最后一个奖金来被恶意对待。
存在漏洞的代码示例 (PoolTogether):
function claimPrizes(...) external returns (uint256 totalFees) {
vault.claimPrizes(tier, winners, prizeIndices, feePerClaim, _feeRecipient);
// 如果任何奖金已经被申领,则还原!
}
影响:申领机器人会损失 gas 成本,降低申领动力。
缓解措施:允许对已申领的奖金进行静默失败。
296. 许可调用者限制 (PoolTogether M-25)
模式:许可函数仅适用于直接签名者。
存在漏洞的代码示例 (PoolTogether):
function depositWithPermit(...) external returns (uint256) {
_permit(IERC20Permit(asset()), msg.sender, address(this), _assets, _deadline, _v, _r, _s);
// 始终使用 msg.sender,而不是 _receiver!
}
影响:合约无法代表具有许可的用户进行存款。
缓解措施:使用 _receiver 作为许可所有者。
297. 静默传输溢出 (PoolTogether M-26)
模式:传输金额被静默截断为 uint96。
存在漏洞的代码示例 (PoolTogether):
function _transfer(address _from, address _to, uint256 _shares) internal virtual override {
_twabController.transfer(_from, _to, uint96(_shares)); // 静默截断!
}
影响:集成协议中的会计错误。
缓解措施:对所有转换使用 SafeCast。
298. Canary Claim 费用排除 (PoolTogether M-27)
模式:费用计算不包括 canary claims。
存在漏洞的代码示例 (PoolTogether):
uint96 feePerClaim = uint96(
_computeFeePerClaim(
_computeMaxFee(tier, prizePool.numberOfTiers()),
claimCount,
prizePool.claimCount() // 应该包括 canaryClaimCount!
)
);
影响:申领者的费用计算不正确。
缓解措施:将 canary claims 包含在总计数中。
299. ERC4626 AutoRollers 中的初始存款操纵 (Sense)
模式:第一个存款人通过存入大量资金来抬高 share price,迫使后续存款人贡献不成比例的价值。
存在漏洞的代码示例 (Sense):
function previewMint(uint256 shares) public view virtual returns (uint256) {
uint256 supply = totalSupply;
return supply == 0 ? shares : shares.mulDivUp(totalAssets(), supply);
}
影响:未来的存款人被迫存入巨额价值,实际上是对普通用户进行 DoS 攻击。
缓解措施:
- 要求使用一部分刻录或发送给 DAO 的最小初始铸币量
- 使用初始种子流动性进行部署
- 虚拟 shares 偏移
300. 公共批准函数 DoS (Sense)
模式:不受保护的公共 approve()
函数可以被抢跑以将 allowance 设置为 0。
存在漏洞的代码示例 (Sense):
function approve(ERC20 token, address to, uint256 amount) public payable {
token.safeApprove(to, amount); // 任何人都可以使用 amount = 0 来调用
}
影响:完全 DoS 的存款/铸币功能。
缓解措施:将 approve() 限制为授权调用者或仅允许最大批准。
301. 通过退出机制进行 Yield 盗窃 (Sense)
模式:结合了 yield-bearing的仓位的退出函数可能会意外地将整个协议 yield 转移给单个用户。
存在漏洞的代码示例 (Sense):
function eject(...) public returns (uint256 assets, uint256 excessBal, bool isExcessPTs) {
(excessBal, isExcessPTs) = _exitAndCombine(shares);
_burn(owner, shares);
// 从所有 YT 转移包括 yield 在内的整个余额!
assets = asset.balanceOf(address(this));
asset.transfer(receiver, assets);
}
影响:用户收到来自整个 vault 的 yield,而不仅仅是他们按比例分配的份额。
缓解措施:计算并仅转移用户按比例分配的组合资产份额。
302. 系列创建竞争条件 (Sense)
模式:多个合约在同一适配器上创建系列可能会通过到期日冲突互相破坏。
存在漏洞的代码示例 (Sense):
function create(address adapter, uint256 maturity) external returns (address pool) {
_require(pools[adapter][maturity] == address(0), Errors.POOL_ALREADY_EXISTS);
// 如果另一个 AutoRoller 在同一到期日创建了系列,则还原
}
攻击:创建具有不同持续时间的 AutoRoller,从而产生冲突的到期日时间戳。
影响:原始 AutoRoller 永久损坏,无法滚动到新系列。
缓解措施:允许加入现有系列或实施冲突解决。
303. 集中流动性 Vault 中的管理员函数夹层攻击 (Beefy)
模式:在没有冷静期检查的情况下重新部署流动性的管理员函数可能会被夹层攻击以耗尽资金。
存在漏洞的代码示例 (Beefy):
function setPositionWidth(int24 _width) external onlyOwner {
_claimEarnings();
_removeLiquidity();
positionWidth = _width;
_setTicks(); // 获取当前 tick 而无需冷静期检查
_addLiquidity(); // 以操纵的价格部署
}
function unpause() external onlyManager {
_isPaused = false;
_setTicks(); // 获取当前 tick 而无需冷静期检查
_addLiquidity(); // 以操纵的价格部署
}
攻击流程:
- 攻击者使用大额 swap 进行抢跑,从而抬高价格
- 管理员交易执行,以虚高的范围部署流动性
- 攻击者进行尾随,以虚高的价格卖入部署的流动性
影响:完全耗尽协议资金(已演示 $1.2M+)。
缓解措施:将 onlyCalmPeriods
修饰符添加到管理员函数或 _setTicks
。
304. 费用 Swaps 中缺少 Slippage 保护 (Beefy)
模式:amountOutMinimum: 0
的协议费用 swaps 容易受到 MEV 的攻击。
存在漏洞的代码示例 (Beefy):
function swap(address _router, bytes memory _path, uint256 _amountIn) internal returns (uint256 amountOut) {
IUniswapRouterV3.ExactInputParams memory params = IUniswapRouterV3.ExactInputParams({
path: _path,
recipient: address(this),
deadline: block.timestamp,
amountIn: _amountIn,
amountOutMinimum: 0 // 没有 slippage 保护!
});
}
影响:由于夹层攻击,协议费用减少。
缓解措施:链下计算最小输出并作为参数传递。
305. 舍入误差导致的费用累积 (Beefy)
模式:费用分配中的除法舍入导致永久的 token 累积。
存在漏洞的代码示例 (Beefy):
function _chargeFees() private {
uint256 callFeeAmount = nativeEarned * fees.call / DIVISOR;
IERC20(native).safeTransfer(_callFeeRecipient, callFeeAmount);
uint256 beefyFeeAmount = nativeEarned * fees.beefy / DIVISOR;
IERC20(native).safeTransfer(beefyFeeRecipient, beefyFeeAmount);
uint256 strategistFeeAmount = nativeEarned * fees.strategist / DIVISOR;
IERC20(native).safeTransfer(strategist, strategistFeeAmount);
// 由于舍入,剩余部分卡住
}
影响:累计费用损失永久卡在合约中。
缓解措施:将剩余部分发送给一个接收者:
uint256 beefyFeeAmount = nativeEarned - callFeeAmount - strategistFeeAmount;
306. 路由器更新上的陈旧 Allowances (Beefy)
模式:更新路由器地址时未删除 token allowances。
存在漏洞的代码示例 (Beefy):
function setUnirouter(address _unirouter) external onlyOwner {
unirouter = _unirouter; // 旧路由器保留 allowances!
emit SetUnirouter(_unirouter);
}
function _giveAllowances() private {
IERC20(lpToken0).forceApprove(unirouter, type(uint256).max);
IERC20(lpToken1).forceApprove(unirouter, type(uint256).max);
}
影响:旧路由器可以继续花费协议 tokens。
缓解措施:覆盖 setUnirouter
以在更新之前删除 allowances。
307. 冷静期 MIN/MAX Tick 边缘情况 (Beefy)
模式:onlyCalmPeriods
检查在 tick 边界处失败。
存在漏洞的代码示例 (Beefy):
function _onlyCalmPeriods() private view {
int24 tick = currentTick();
int56 twapTick = twap();
if(twapTick - maxTickDeviationNegative > tick || // 可能会下溢到 MIN_TICK 以下
twapTick + maxTickDeviationPositive < tick) revert NotCalm();
}
影响:在极端价格下 DoS 攻击存款、取款和收获。
缓解措施:
int56 minCalmTick = max(twapTick - maxTickDeviationNegative, MIN_TICK);
int56 maxCalmTick = min(twapTick + maxTickDeviationPositive, MAX_TICK);
308. 通过回收存款进行 Share Price 操纵 (Beefy)
模式:第一个存款人可以通过存款/取款周期大量抬高 share count。
攻击流程:
- 第一个存款人存入初始金额
- 重复:全部取款 → 全部存款
- Share count 随着每个周期而膨胀
影响:虽然 share count 膨胀,但没有发现直接的盗窃机制。
缓解措施:重新设计 share 计算逻辑以防止回收收益。
309. 集中流动性 Tick 更新差距 (Beefy)
模式:在某些路径中,流动性部署之前缺少 tick 更新。
存在漏洞的代码示例 (Beefy):
function withdraw() external {
_removeLiquidity();
// 这里缺少 _setTicks()!
_addLiquidity(); // 使用陈旧的 tick 数据
}
影响:非最佳流动性仓位,LP 奖励减少。
缓解措施:确保在所有 _addLiquidity()
调用之前调用 _setTicks()
。
310. 尽管有正存款,但铸造零 Shares (Beefy)
模式:舍入和最小 share 减少可能会导致零 shares。
存在漏洞的代码示例 (Beefy):
function deposit() external {
uint256 shares = _amount1 + (_amount0 * price / PRECISION);
if (_totalSupply == 0 && shares > 0) {
shares = shares - MINIMUM_SHARES; // 可以使 shares = 0!
_mint(address(0), MINIMUM_SHARES);
}
_mint(receiver, shares); // 铸造 0 shares!
}
影响:用户丢失存款 tokens,但未收到任何 shares。
缓解措施:在所有计算后添加零 share 检查。
311. 大型 sqrtPriceX96 的价格计算溢出 (Beefy)
模式:价格计算中的平方运算对于有效的 Uniswap 价格溢出。
存在漏洞的代码示例 (Beefy):
function price(uint160 sqrtPriceX96) internal pure returns (uint256 _price) {
_price = FullMath.mulDiv(uint256(sqrtPriceX96) ** 2, PRECISION, (2 ** 192));
// 对于 sqrtPriceX96 > 3.4e38 溢出
}
影响:DoS 攻击存款和其他与价格相关的函数。
缓解措施:重构以避免中间溢出。
312. Block.timestamp 作为到期日不提供任何保护 (Beefy)
模式:在 swaps 中使用当前时间戳作为到期日。
存在漏洞的代码示例 (Beefy):
IUniswapRouterV3.ExactInputParams memory params = IUniswapRouterV3.ExactInputParams({
deadline: block.timestamp, // 始终通过!
// ...
});
影响:无法防止交易延迟或验证者操纵。
缓解措施:接受调用者作为参数传递的到期日。
313. 集中流动性 pool.slot0 操纵风险 (Beefy)
模式:从 slot0 读取当前价格/tick 会导致各种攻击。
使用点:
- 设置流动性范围
- 计算存款 shares
- 价格转换
影响:尽管有冷静期检查,但任何实施差距都会导致耗尽攻击。
缓解措施:保持严格的冷静期执行,考虑对关键操作使用 TWAP。
314. 升级合约中缺少存储间隙 (Beefy)
模式:没有存储间隙的升级合约存在插槽冲突的风险。
存在漏洞的代码示例 (Beefy):
contract StratFeeManagerInitializable is Initializable, OwnableUpgradeable {
// 状态变量但没有 __gap!
}
影响:升级时的存储冲突可能会破坏子合约状态。
缓解措施:添加存储间隙:uint256[50] private __gap;
315. 升级合约缺少 disableInitializers (Beefy)
模式:实现合约可以在不应该初始化时被初始化。
存在漏洞的代码示例 (Beefy):
contract StrategyPassiveManagerUniswap is StratFeeManagerInitializable {
// 没有调用 _disableInitializers() 的构造函数!
}
缓解措施:
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
316. 所有者通过冷静期参数操纵进行 Rug-Pull (Beefy)
模式:所有者可以禁用保护机制以耗尽资金。
攻击流程:
- 所有者使用较大的值调用
setDeviation
或调用setTwapInterval(1)
- 通过闪贷操纵池价格
- 以虚高的 share price 存款
- 以正常价格取款以获取利润
缓解措施:强制执行最小安全参数范围。
317. 取款为正 Shares 返回零 Tokens (Beefy)
模式:取款计算中的舍入可能会不返回任何内容。
存在漏洞的代码示例 (Beefy):
function withdraw(uint256 _shares) external {
uint256 _amount0 = (_bal0 * _shares) / _totalSupply; // 可以舍入为 0
uint256 _amount1 = (_bal1 * _shares) / _totalSupply; // 可以舍入为 0
}
缓解措施:如果两个金额均为零,则还原。
318. 来自捐赠 Shares 的永久 Token 锁定 (Beefy)
模式:第一个存款人捐赠的 shares 会创建永久锁定的 tokens。
机制:发送到 address(0) 的 MINIMUM_SHARES
表示永远无法取出的 tokens。
缓解措施:添加生命周期结束函数以在 totalSupply == MINIMUM_SHARES
时恢复。
319. 多市场存款协调失败 (Silo M-01)
模式:Vault 尝试将全部金额存入每个市场,而不检查各个市场限制。
存在漏洞的代码示例 (Silo):
function _supplyERC4626(uint256 _assets) internal virtual {
for (uint256 i; i < supplyQueue.length; ++i) {
IERC4626 market = supplyQueue[i];
uint256 toSupply = UtilsLib.min(UtilsLib.zeroFloorSub(supplyCap, supplyAssets), _assets);
if (toSupply != 0) {
try market.deposit(toSupply, address(this)) { // 如果 toSupply > market.maxDeposit!则还原
_assets -= toSupply;
} catch {}
}
}
}
影响:即使多个市场存在足够的空间,存款也会失败。
缓解措施:在尝试存款之前检查 market.maxDeposit:
toSupply = Math.min(market.maxDeposit(address(this)), toSupply);
320. 转移期间的奖励应计Timing Error (Silo M-02)
模式:转移 hooks 声明奖励,而无需首先通过费用应计更新 totalSupply。
存在漏洞的代码示例 (Silo):
function _update(address _from, address _to, uint256 _value) internal virtual override {
_claimRewards(); // 在没有首先更新 totalSupply 的情况下声明!
super._update(_from, _to, _value);
}
影响:由于陈旧的 totalSupply,奖励分配不正确。
缓解措施:在转移流程中的 _claimRewards() 之前添加 _accrueFee()。
321. 零还原 Tokens 的市场移除 DOS (Silo M-03)
模式:Tokens 在零批准时还原会阻止市场移除。
存在漏洞的代码示例 (Silo):
function setCap(...) external {
if (_supplyCap > 0) {
approveValue = type(uint256).max;
}
// 对于 cap = 0,approveValue 保持为 0
IERC20(_asset).forceApprove(address(_market), approveValue); // 对于 BNB!还原
}
影响:无法删除具有零还原 tokens 的市场。
缓解措施:移除市场时,将 approveValue 设置为 1 而不是 0。
322. 核心操作中缺少 Slippage 保护 (Silo M-04)
模式:在存款/取款/赎回函数中没有用户指定的 slippage 容差。
存在漏洞的代码示例 (Silo):
function deposit(uint256 assets, address receiver) public returns (uint256 shares) {
// 没有 minShares 参数!
shares = previewDeposit(assets);
_deposit(msg.sender, receiver, assets, shares);
}
影响:用户容易受到夹层攻击和不利的价格变动的影响。
缓解措施:添加 minShares/minAssets 参数以保护用户。
323. 费用 Share 铸造顺序导致奖励损失 (Silo M-05)
模式:在奖励分配后铸造费用 shares,缺少当前周期奖励。
存在漏洞的代码示例 (Silo):
function claimRewards() public virtual {
_updateLastTotalAssets(_accrueFee()); // 铸造费用 shares
_claimRewards(); // 但奖励已经在 _accrueFee 中分配!
}
function _accrueFee() internal virtual returns (uint256 newTotalAssets) {
if (feeShares != 0) _mint(feeRecipient, feeShares); // 触发 _update
}
function _update(address _from, address _to, uint256 _value) internal virtual override {
_claimRewards(); // 在铸造费用 shares 之前分配奖励!
super._update(_from, _to, _value);
}
影响:费用接收者永久丢失每个利息应计期间的奖励。
缓解措施:实施基于标志的逻辑来专门处理费用 share 铸造。
324. 通过市场舍入的通货紧缩攻击 (Silo M-06)
模式:可以利用市场舍入来降低 share price,直到接近溢出。
存在漏洞的代码示例 (Silo):
// 1 wei 的首次存款
market.deposit(1, address(this)); // 市场舍入为 0,不返回 shares
// 下一次存款将 shares 计算为:
// 1 wei * (10**decimalsOffset + 1) / (0 + 1) = 2 * 10**decimalsOffset shares
// 重复的 1 wei 存款每次都会使 totalSupply 翻倍!
影响:Share price 通货紧缩使 vault 损坏或奖励垄断成为可能。
缓解措施:将虚拟资产设置为等于虚拟 shares (10**DECIMALS_OFFSET)。
325. 未经检查的两步所有权转移 (Dacian)
模式:所有权转移的第二步不验证是否已启动第一步。
存在漏洞的代码示例:
function completeNodeOwnerTransfer(uint64 id) external {
uint64 newOwner = pendingNodeOwnerTransfers[id]; // 如果未启动,则为 0
uint64 accountId = accounts.resolveId(msg.sender); // 如果未注册,则为 0
if (newOwner != accountId) revert NotAuthorizedForNode();
nodes[id].owner = newOwner; // 设置为 0!
delete pendingNodeOwnerTransfers[id];
}
影响:攻击者可以通过将所有者设置为零来破坏节点所有权。
缓解措施:要求 newOwner != 0 或验证是否启动了转移。
326. 意外的匹配输入 (Dacian)
模式:函数假定不同的输入,但在具有相同输入的情况下会灾难性地失败。
存在漏洞的代码示例:
function _getTokenIndexes(IERC20 t1, IERC20 t2) internal pure returns (uint i, uint j) {
for (uint k; k < _tokens.length; ++k) {
if (t1 == _tokens[k]) i = k;
else if (t2 == _tokens[k]) j = k; // 如果 t1==t2!则永远不会执行
}
}
影响:当 t1==t2 时返回 (i, 0),破坏不变式并启用资金耗尽。
缓解措施:添加验证:require(t1 != t2) 或正确处理相同输入。
327. 意外的空输入数组 (Dacian)
模式:函数假定非空数组,允许跳过验证。
存在漏洞的代码示例:
function verifyAndSend(SigData[] calldata signatures) external {
for (uint i; i<signatures.length; i++) {
// 验证签名
}
// 空数组会跳过验证!
(bool sent,) = payable(msg.sender).call{value: 1 ether}("");
require(sent, "Failed");
}
影响:完全绕过签名验证。
缓解措施:在处理之前,要求 signatures.length > 0。
328. 未经检查的返回值 (Dacian)
模式:忽略关键函数的返回值,从而导致状态损坏。
存在漏洞的代码示例:
function commitCollateral(uint loanId, address token, uint amount) external {
CollateralInfo storage collateral = _loanCollaterals[loanId];
collateral.collateralAddresses.add(token); // 如果已存在,则返回 false!
collateral.collateralInfo[token] = amount; // 覆盖现有金额!
}
影响:借款人在贷款批准后可以将抵押品减少到 0。
缓解措施:始终检查返回值:require(collateral.collateralAddresses.add(token), "Already exists");
329. 向下舍入为零(增强)
模式:Solidity 中的除法向下舍入,这可能导致关键值变为零,尤其是在数字较小的情况下。
存在漏洞的代码示例(Cooler):
function errorRepay(uint repaid) external {
// 如果 repaid 足够小,则 decollateralized 将向下舍入为 0
uint decollateralized = loanCollateral * repaid / loanAmount;
loanAmount -= repaid;
loanCollateral -= decollateralized;
}
影响:可以在不减少抵押品的情况下偿还贷款,从而允许借款人提取价值。
缓解措施:
function correctRepay(uint repaid) external {
uint decollateralized = loanCollateral * repaid / loanAmount;
// 不允许在不从抵押品中扣除的情况下偿还贷款
if(decollateralized == 0) { revert("Round down to zero"); }
loanAmount -= repaid;
loanCollateral -= decollateralized;
}
检测启发式:
- 查找用于更新关键状态的除法
- 检查小输入值是否会导致舍入为零
- 考虑零结果是否会破坏协议不变式
330. 没有精度缩放(增强)
模式:在没有适当缩放的情况下组合具有不同小数精度的 tokens 金额。
存在漏洞的代码示例(Notional):
function errorGetWeightedBalance(...) external view returns (uint256 primaryAmount) {
uint256 primaryBalance = token1Amount * lpPoolTokens / poolTotalSupply;
uint256 secondaryBalance = token2Amount * lpPoolTokens / poolTotalSupply;
uint256 secondaryAmountInPrimary = secondaryBalance * lpPoolTokensPrecision / oraclePrice;
// 添加具有不同精度的余额!
primaryAmount = (primaryBalance + secondaryAmountInPrimary) * token1Precision / lpPoolTokensPrecision;
}
影响:DAI/USDC 池中的 LP tokens 被大幅低估约 50%。
缓解措施:
function correctGetWeightedBalance(...) external view returns (uint256 primaryAmount) {
uint256 primaryBalance = token1Amount * lpPoolTokens / poolTotalSupply;
uint256 secondaryBalance = token2Amount * lpPoolTokens / poolTotalSupply;
// 首先将辅助 token 缩放到主 token 的精度
secondaryBalance = secondaryBalance * token1Precision / token2Precision;
uint256 secondaryAmountInPrimary = secondaryBalance * lpPoolTokensPrecision / oraclePrice;
primaryAmount = primaryBalance + secondaryAmountInPrimary;
}
331. 过度精度缩放(增强)
模式:将精度缩放多次应用于已缩放的值。
影响:Tokens 金额变得过度膨胀,破坏了计算。
检测:跟踪 tokens 金额通过函数的流动,以识别重复的缩放操作。
332. 精度缩放不匹配(增强)
模式:不同模块使用不同的精度假设(小数与 1e18)。
存在漏洞的代码示例(Yearn):
// Vault.vy 使用 tokens 小数
def pricePerShare() -> uint256:
return self._shareValue(10 ** self.decimals)
// YearnYield 使用硬编码的 1e18
function getTokensForShares(uint256 shares) public view returns (uint256) {
amount = IyVault(liquidityToken[asset]).getPricePerFullShare().mul(shares).div(1e18);
}
影响:对于非 18 小数 tokens 的计算不正确。
缓解措施:确保所有模块之间的一致精度处理。
333. 舍入从协议中泄漏价值(增强)
模式:费用计算的舍入有利于用户而不是协议。
存在漏洞的代码示例(SudoSwap):
// 向下舍入有利于交易者
protocolFee = outputValue.mulWadDown(protocolFeeMultiplier);
tradeFee = outputValue.mulWadDown(feeMultiplier);
缓解措施:
// 向上舍入以支持协议
protocolFee = outputValue.mulWadUp(protocolFeeMultiplier);
tradeFee = outputValue.mulWadUp(feeMultiplier);
影响:随着时间的推移,价值会从协议系统地泄漏到交易者。
334. 清算Timing Error漏洞
模式:围绕何时以及如何进行清算的复杂漏洞。
关键子模式:
- 违约前清算:借款人在未付款之前被清算
- 缺少宽限期:暂停后没有恢复时间
- 部分清算游戏:使用部分清算来避免坏账
- 鲸鱼仓位锁定:没有闪贷,无法清算大仓位
- 恢复后立即清算:暂停后清算机器人的优势
检测启发式:
- 验证是否只有在实际违约后才有可能进行清算
- 检查状态更改后是否存在宽限期
- 确保部分清算正确处理坏账
- 验证可以清算鲸鱼仓位
- 测试暂停/取消暂停周期周围的清算 Timing Error
335. 坏账和激励错位
模式:协议未能正确激励清算或处理无力偿债的仓位。
关键子模式:
- 没有清算激励:缺少清算人的奖励
// 攻击者创建许多仓位以导致 OOG function getItemIndex(uint256[] memory items, uint256 item) internal pure returns (uint256) { for (uint256 i = 0; i < items.length; i++) { // OOG with many items if (items[i] == item) return i; } }
337. 清算计算错误
模式:清算奖励和费用计算中的数学错误。
常见问题:
- 小数精度不匹配:债务/抵押品的小数位数差异
- 协议费用计算错误:费用基于扣押金额而非利润
- 奖励缩放错误:线性缩放在多个账户中失效
- 利息排除:计算中不包括应计利息
- 错误的 Token 数量:使用未缩放的内部金额
示例:
// 清算人奖励使用债务小数 (6) 进行抵押品计算 (18)
uint256 liquidatorReward = Math.mulDivUp(
debtPosition.futureValue, // 6 decimals
state.feeConfig.liquidationRewardPercent,
PERCENT
); // Result in wrong decimals for WETH collateral
338. 跨协议清算问题
模式:不同抵押品类型之间的清算机制引起的问题。
主要问题:
- 清算人抵押品选择:选择稳定资产而不是波动资产
- 健康评分降低:清算使仓位更糟
- 优先级顺序错乱:错误的清算顺序
- 多抵押品计算:不正确的聚合健康评分
缓解措施:
// 验证清算后借款人健康状况得到改善
uint256 healthBefore = calculateHealthScore(borrower);
// ... 执行清算 ...
uint256 healthAfter = calculateHealthScore(borrower);
require(healthAfter > healthBefore, "Liquidation must improve health");
339. Oracle 价格反转 (USSD)
模式:使用反转的基础/汇率 Token 进行 oracle 价格计算,导致大规模定价错误。
易受攻击的代码示例:
// 使用来自 Uniswap 池的 WETH/DAI
uint256 DAIWethPrice = DAIEthOracle.quoteSpecificPoolsWithTimePeriod(
1000000000000000000, // 1 Eth
0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, // WETH (base)
0x6B175474E89094C44Da98b954EedeAC495271d0F, // DAI (quote)
pools,
600
);
// 但使用来自 Chainlink 的 DAI/ETH
(, int256 price, , , ) = priceFeedDAIETH.latestRoundData();
// 平均不兼容的价格格式!
return (wethPriceUSD * 1e18) / ((DAIWethPrice + uint256(price) * 1e10) / 2);
影响:不正确的平均计算导致价格非常不准确。
缓解措施:确保两个价格来源使用相同的基础/报价顺序,或在平均之前反转一个。
340. 逻辑运算符错误 (USSD)
模式:在条件检查中使用 || 代替 &&,导致不正确的逻辑执行。
易受攻击的代码示例:
// 错误:应该是 && 以排除 DAI
if (collateral[i].token != uniPool.token0() || collateral[i].token != uniPool.token1()) {
// 始终为真 - 即使没有路径,也会尝试出售 DAI
IUSSD(USSD).UniV3SwapInput(collateral[i].pathsell, amountToSellUnits);
}
影响:尝试出售没有出售路径的 DAI,导致重新平衡还原。
缓解措施:使用正确的逻辑运算符:
if (collateral[i].token != uniPool.token0() && collateral[i].token != uniPool.token1())
341. 价格计算公式错误 (USSD)
模式:Uniswap V3 价格计算中不正确的数学公式。
易受攻击的代码示例:
// 当 token0 是 USSD 时
price = uint(sqrtPriceX96)*(uint(sqrtPriceX96))/(1e6) >> (96 * 2);
// 应该乘以 1e6,而不是除以!
// 当 token1 是 USSD 时
price = uint(sqrtPriceX96)*(uint(sqrtPriceX96))*(1e18) >> (96 * 2);
// 应该使用 1e6,而不是 1e18!
影响:大规模定价错误影响所有重新平衡操作。
缓解措施:按照文档使用正确的 Uniswap V3 价格计算公式。
342. Oracle 小数假设 (USSD)
模式:假设 oracle 响应的固定小数位数值,而实际上它们是变化的。
易受攻击的代码示例:
// 假设 DAI/ETH oracle 返回 8 位小数
return (wethPriceUSD * 1e18) / ((DAIWethPrice + uint256(price) * 1e10) / 2);
// 但 DAI/ETH 实际上返回 18 位小数!
影响:DAI 价格被高估 10^10 倍,允许大规模利用。
缓解措施:检查 oracle 的 decimals() 或验证实际小数位数。
343. Uniswap V3 Slot0 价格操纵 (USSD)
模式:使用瞬时 slot0 价格而不是 TWAP,从而实现闪贷攻击。
易受攻击的代码示例:
function getOwnValuation() public view returns (uint256 price) {
(uint160 sqrtPriceX96,,,,,,) = uniPool.slot0();
// 使用可操作的现货价格!
}
影响:攻击者可以操纵价格以触发有利的重新平衡。
缓解措施:在合理的时间段(例如 30 分钟)内使用 TWAP 价格。
344. 关键功能上缺少访问控制 (USSD)
模式:铸造/销毁 Token 的功能缺少适当的访问控制。
易受攻击的代码示例:
function mintRebalancer(uint256 amount) public override {
_mint(address(this), amount); // 任何人都可以调用!
}
function burnRebalancer(uint256 amount) public override {
_burn(address(this), amount); // 任何人都可以调用!
}
影响:攻击者可以铸造到最大供应量,操纵 totalSupply 进行重新平衡。
缓解措施:添加 onlyBalancer
修饰符以限制访问。
345. 基于 Uniswap V3 余额的价格假设 (USSD)
模式:假设池 Token 余额反映了集中流动性中的价格。
易受攻击的代码示例:
function getSupplyProportion() public view returns (uint256, uint256) {
return (IERC20Upgradeable(USSD).balanceOf(uniPool), IERC20(DAI).balanceOf(uniPool));
}
// 余额不代表 Uniswap V3 中的价格!
影响:重新平衡计算完全不正确,可能导致下溢。
缓解措施:使用正确的 Uniswap V3 流动性计算,而不是原始余额。
346. Oracle 地址配置错误 (USSD)
模式:关键 oracle 的错误合约地址。
示例:
- StableOracleWBTC 使用 ETH/USD 馈送而不是 BTC/USD
- StableOracleDAI 具有错误的 DAIEthOracle 地址
- StableOracleDAI ethOracle 设置为 address(0)
- StableOracleWBGL 使用池地址而不是 oracle
影响:所有操作的价格完全不正确。
缓解措施:在部署之前验证所有 oracle 地址。
347. Oracle 价格单位不匹配 (USSD)
模式:Oracle 价格以错误的货币计价以用于预期用途。
问题:所有 oracle 都返回美元价格,但系统期望 DAI 价格以维持Hook。
攻击场景:
- 当 DAI > 1 美元时,用户以虚高的汇率用 DAI 铸造 USSD
- 以高于存款价格出售 USSD 以获取更多 DAI
- 系统不正确地重新平衡,耗尽抵押品
影响:完全摧毁Hook机制。
缓解措施:将所有 oracle 价格转换为 DAI 计价。
348. 重新平衡下溢漏洞 (USSD)
模式:重新平衡期间可能下溢的减法运算。
易受攻击的代码示例:
amountToBuyLeftUSD -= (IERC20Upgradeable(baseAsset).balanceOf(USSD) - amountBefore);
// 如果实际交换返回的价值超过预期,则可能下溢
影响:重新平衡还原,协议变得无法维持Hook。
缓解措施:检查结果是否会下溢,如果需要,则上限为零。
349. 数组索引越界 (USSD)
模式:当抵押品因子较高时,Flutter 索引可能超出数组边界。
易受攻击的代码示例:
for (flutter = 0; flutter < flutterRatios.length; flutter++) {
if (cf < flutterRatios[flutter]) {
break;
}
}
// 循环后,flutter 可以等于 flutterRatios.length
// 稍后访问超出范围:
if (collateralval * 1e18 / ownval < collateral[i].ratios[flutter]) {
影响:当抵押品因子超过所有 Flutter 比率时,重新平衡始终会还原。
缓解措施:在数组访问之前检查 flutter < flutterRatios.length。
350. 缺少抵押品资产核算 (USSD)
模式:抵押品因子计算中不包括已删除的抵押品资产。
易受攻击的代码示例:
function removeCollateral(uint256 _index) public onlyControl {
collateral[_index] = collateral[collateral.length - 1];
collateral.pop();
// 合约仍然持有已移除的资产,但未计算在内!
}
影响:抵押品因子被低估,影响风险评估。
缓解措施:转出已移除的抵押品或继续对其进行计数。
351. DAI 抵押品处理不一致 (USSD)
模式:DAI 作为抵押品在出售操作中处理不一致。
易受攻击的代码示例:
// 第一个分支正确处理 DAI
if (collateralval > amountToBuyLeftUSD) {
if (collateral[i].pathsell.length > 0) {
// 出售抵押品
} else {
// 不要出售 DAI
}
} else {
// 第二个分支缺少 DAI 检查!
IUSSD(USSD).UniV3SwapInput(collateral[i].pathsell, ...);
}
影响:尝试出售没有路径的 DAI 会导致还原。
缓解措施:在 else 分支中添加 pathsell.length 检查。
352. 包装资产脱钩风险 (USSD)
模式:使用 BTC 价格购买 WBTC 而不考虑脱钩的可能性。
问题:StableOracleWBTC 使用 BTC/USD 馈送,假设 1:1 平价。
影响:如果 WBTC 脱钩:
- 协议以完整的 BTC 价格对毫无价值的 WBTC 进行估值
- 坏账累积
- 继续针对贬值的抵押品进行铸造
缓解措施:实施具有 WBTC/BTC 比率检查的双重 oracle。
353. 部分抵押品出售精度损失 (USSD)
模式:复杂的除法运算可能会四舍五入为零以进行部分出售。
易受攻击的代码示例:
uint256 amountToSellUnits = IERC20Upgradeable(collateral[i].token).balanceOf(USSD) *
((amountToBuyLeftUSD * 1e18 / collateralval) / 1e18) / 1e18;
// 多次除法可能导致结果为 0
影响:重新平衡无法出售任何抵押品,即使它应该出售部分金额。
缓解措施:重新排序操作以最大限度地减少精度损失。
354. 通过 Oracle 偏差进行套利 (USSD)
模式:以陈旧的 oracle 价格进行铸造可以实现无风险利润。
攻击:当市场价格 < oracle 价格超过偏差阈值时:
- 以 oracle 价格使用抵押品铸造 USSD
- 以市场价格出售 DAI
- 从差额中获利
影响:持续的价值提取,耗尽协议抵押品质量。
缓解措施:添加铸造费用 > 最大 oracle 偏差(例如 1%)。
355. 缺少赎回功能 (USSD)
模式:白皮书承诺赎回功能,但未实现。
问题:无法销毁 USSD 来获取基础抵押品,只有单向转换。
影响:
- 用户无法退出仓位
- 没有套利机制来维持低于Hook的价值
- 违反基本稳定币机制
缓解措施:按照白皮书中的规定实施赎回功能。
常见攻击向量
1. 三明治攻击
- 抢先交易大型存款/取款
- 在用户交易之前/之后操纵份额价格
- 利用收益仓位进入/退出
- 自动赎回 MEV 利用
- 多步骤操作三明治(取款/交换/重新存款)
- 权重更新时机利用
- 基于 Hook 的重入三明治
- 跨链消息抢先交易
- 范围重入时机攻击
- 管理员参数更改利用
- 策略收割操纵
- 许可签名抢先交易
- 清算抢先交易
- 利率操纵时机
- 部分清算操纵
- 仓位操作交换三明治
- ERC4626 汇率操纵
- 折扣费用交易利用
- yDUSD 金库存款三明治
- FundingRateArbitrage 份额价格操纵
- 清算前的再融资攻击
- 承诺重放利用
- 荷兰式拍卖价格操纵
- 收购留置权抢先交易
- 复合函数 MEV 利用
- 汇率捐赠攻击 (PoolTogether)
- Hook 执行时机攻击 (PoolTogether)
- 基于 Permit 的操作抢先交易 (PoolTogether)
- 系列创建抢先交易 (Sense)
- CL 金库中的管理员函数三明治攻击 (Beefy)
- 多市场存款时机利用 (Silo)
- 自我清算的 Oracle 更新三明治
2. 闪贷攻击
- 操纵 oracle 价格
- 用于操作的临时抵押品
- 利率操纵
- 放大自我支持问题
- 强制自动赎回触发器
- 集中池中的 JIT 流动性操纵
- 加权池中的权重比率操纵
- 重入保护绕过放大
- CCIP 消息操纵
- 捐赠前的份额价格操纵
- 自我清算放大
- 复利利用
- CDP 仓位操纵
- 绕过清算比率
- 仓位杠杆利用
- 奖励分配时机攻击
- 折扣费用放大
- 空头仓位操纵
- FundingRateArbitrage 指数操纵
- 清算价格操纵
- 收购验证绕过
- 公共金库斜率操纵
- 通过 Uniswap V3 slot0 操纵 Oracle 价格
- 盈利的通过 Oracle 更新自我清算
- 利用 Oracle 价格倒置进行套利
- 操纵重新平衡触发器,通过现货价格变化
- 创建人为抵押品估值,通过 TWAP 操纵
- 以不利汇率强制重新平衡
- 套利 Oracle 偏差
- 影响甲骨文的价格通过 Uniswap V3 的 slot0
- 利用 oracle 价格反转进行套利
- 通过现货价格变化操纵再平衡触发器
- 通过 TWAP 操纵创建人为抵押品估值
- 以不利汇率强制重新平衡
- 根据过时的价格套利 oracle 偏差(铸币)
3. 恶意攻击
- 通过最低存款阻止操作
- 通过过度 gas 消耗进行 DOS
- 操纵排序的数据结构
- 使用还原 Token 阻止清算
- 通过发送 Token 直接到金库导致下溢
- 通过操纵时机参数来阻止权限更新
- 利用排队退出宽限期
- 阻止跨链消息
- 触发储备份额溢出
- 在首次存款之前直接转移到策略
- 路由器存款限制阻止
- 通过灰尘偿还进行抢先清算交易
- 奖励分配期间延长
- 零数量赞助攻击(PoolTogether)
- 最小份额违规行为
- 每日奖励归属垃圾邮件
- 许可 nonce 耗尽
- 仓位操作 辅助交换阻止
- 排放计划操纵
- 基于余额的计算操纵
- yDUSD 金库捐赠攻击
- FundingRateArbitrage 中的取款请求垃圾邮件
- 留置权转移到未创建的金库
- 清算中心任意调用
- 零转账还原
- ERC777 回调还原
- 策略师收购预防
- GMX 冷却利用(每年 3.5k 美元以阻止所有赎回)
- vGMX/vGLP 转移阻止
- 强制委派移除(PoolTogether)
- 单个金丝雀索赔级别扩展(PoolTogether)
- 基于 Hook 的 DoS 攻击(PoolTogether)
- 零金额赞助攻击(PoolTogether)
- 公开批准 DoS (Sense)
- 份额回收通货膨胀 (Beefy)
- 零还原 Token 市场移除 DoS (Silo)
- 通过许多小仓位进行清算 DoS
- 待处理操作清算阻止
- 强制重新平衡操作以通过计算的交易还原
- 利用数组越界来永久破坏重新平衡
- 操纵 Flutter 比率以导致系统性故障
- 创建导致重新平衡计算中下溢的仓位
4. MEV 利用
- 交易顺序操纵
- 清算的捆绑攻击
- 价格更新的套利
- 收益存款/取款利用
- 自动赎回抢先交易
- 多操作三明治攻击
- 权重更新抢先交易
- 跨池重新平衡套利
- 跨链套利
- 范围转换期间的费用捕获
- 具有 ValueTokens 的交叉清算套利
- 收割时机操纵
- 清算竞争条件
- 复利利用
- 仓位操作套利
- 滑点利用
- 奖励索赔抢先交易
- CDP 清算 MEV
- 闪贷三明治攻击
- ERC4626 操作时机
- 折扣交易套利
- 空头订单操纵
- FundingRateArbitrage 进入/退出时机
- 清算前的再融资
- 承诺重放时机
- 荷兰式拍卖结算
- 转移储备抢先交易
- 复合函数参数操纵
- 奖品索赔 MEV (PoolTogether)
- 金库贡献时机 (PoolTogether)
- 级别赔率操纵 (PoolTogether)
- 费用交换 MEV 利用 (Beefy)
- 奖励分配时机 (Silo)
- 清算 Oracle 更新利用
5. 跨链攻击
- 跨链转移期间的份额价值操纵
- 跨链操作抢先交易
- 利用铸币/销毁与锁定/解锁机制
- 消息重放攻击
- Bridge 时机利用
- 源链验证绕过
- 链 ID 类型不匹配
- 消息解码失败
- 跨链清算延迟
6. 外部协议操纵
- 集成协议中的无需许可函数
- 绕过预期的奖励流程
- 来自外部调用的状态不一致
- 暂停相关的 DoS 向量
- 基于回调的重入
- Hook 权限利用
- 缺少接口实现
- 金库集成中的费用绕过
- 暂停协议级联故障
- 不推荐使用的函数使用
- 仓位操作协议交互
- ERC4626 汇率攻击
- 取款中的任意调用利用
- 阻止列表 Token 利用
- 持票资产操纵
- 清算中心利用
- GMX 冷却参数利用
- 直接奖励索赔绕过
- 不兼容的收益金库行为 (PoolTogether)
- Hook 实现攻击 (PoolTogether)
- AutoRollers 之间的系列冲突 (Sense)
- 精耕细作的金库中的 Uniswap V3 slot0 依赖项 (Beefy)
- 特定于市场的限制利用 (Silo)
- 通过收益金库抵押品进行清算拒绝
- 利用中心流动性中的 Uniswap V3 余额假设
- 通过错误的甲骨文配置进行获利
- Chainlink oracle 小数位假设利用
- 断路器边缘情况利用
- 错误的 oracle 地址利用,用于不正确的估值
7. 会计操纵
- 直接 Token 转移破坏内部会计
- 计算中的份额/资产混淆
- 跨协议的十进制处理错误
- 利息计算不匹配
- 费用分配稀释
- 奖励覆盖漏洞
- 基于余额的计算利用
- 抵押品跟踪错误
- 仓位状态不一致
- 多金库会计损坏
- 阶段转换会计错误
- 存储插槽计算错误
- 打包数据处理错误
- yDUSD totalSupply 取消同步
- FundingRateArbitrage 指数操纵
- 舍入方向利用
- 收购中的斜率会计错误
- 收益计算损坏
- 取款储备计算错误
- Epoch 处理错误
- 复利利用
- 动态排放率计算错误
- 小仓位奖励损失
- 汇率计算缺陷 (PoolTogether)
- 储备注入跟踪 (PoolTogether)
- TWAB 余额操纵 (PoolTogether)
- 资产/份额类型混淆 (PoolTogether)
- 通过退出机制窃取收益 (Sense)
- 来自舍入的费用累积 (Beefy)
- 费用份额时序错误 (Silo)
- 清算会计差异
- 利用抵押品因子计算差距
- 通过已移除的抵押品操纵重新平衡
- Uniswap V3 集成中的价格计算公式错误
- Oracle 单位不匹配利用(USD 与 DAI 计价)
- 部分抵押品出售精度损失
8. 抵押品攻击
- 利用抵押不足的状态
- 阻止金库恢复
- 基于捐赠的攻击
- 精度损失触发虚假的抵押不足
- 收益金库兼容性问题
- 汇率操纵
- 抵押品检查时机
- 边缘情况下零可取款资产
- 收费转移破坏假设
- 实际违约之前的清算
- 创建低于清算阈值的仓位
9. 类型安全漏洞
- 静默向下转换溢出
- 不安全的类型转换
- 奖品大小溢出
- 费用计算溢出
- 斜率/y 截距溢出
- 时间戳溢出
- 余额类型不匹配
- 转移中的静默截断
- 价格计算溢出 (Beefy)
- 清算金额溢出
10. 奖品分配攻击
- TWAB 时间范围操纵
- 阻止观察创建
- 金库贡献计算错误
- 等级扩展操纵
- 抽奖管理器控制
- 储备基金盗窃
- 不正确的赔率计算
- 索赔激励措施调整
11. 输入验证攻击
- 未经检查的所有权转移利用
- 匹配输入绕过漏洞
- 空数组验证绕过
- 未经检查的返回值利用
- 缺少参数验证
- 类型混淆攻击
- 边界条件利用
- 零/最大值边缘情况
12. 特定于清算的攻击向量
- 自我清算以获取利润
- 清算时机操纵
- 部分清算游戏
- 抵押品选择利用
- 健康评分操纵
- 避免坏账
- 保险基金耗尽
- Oracle 三明治清算
- 交叉仓位清算阻止
- 清算奖励盗窃
13. 特定于 Oracle 的攻击向量
- 价格倒置攻击:利用倒置的基础/报价 Token 对来获取不正确的价格
- 小数位假设攻击:在 oracle 使用不同的小数位时,利用硬编码的小数位假设
- 陈旧价格接受:在缺少新鲜度检查时,使用过时的价格
- 断路器利用:利用 oracle 中的最小/最大价格限制
- 多 Oracle 平均攻击:在平均多个 oracle 时,利用不兼容的价格格式
- 现货与 TWAP 套利:在协议期望 TWAP 时,使用现货价格操纵
- Oracle 地址错误配置:利用错误的 oracle 地址(例如,BTC 的 ETH/USD)
- 单位面额攻击:在以 DAI 计价的系统中,利用以美元计价的 oracle
14. 重新平衡机制攻击
- 下溢攻击:精心设计导致重新平衡数学运算下溢的交换
- Flutter 比率操纵:推动抵押品比率以导致数组越界
- 逻辑运算符利用:利用 || 与 && 错误来强制执行意外行为
- 精度损失攻击:利用四舍五入为零的除法繁重的计算
- DAI 路径利用:强制重新平衡尝试出售没有配置路径的 DAI
- 抵押品移除游戏:在计算中利用移除但持有的抵押品
- 部分出售精度攻击:使部分抵押品出售四舍五入为零
15. 特定于稳定币的攻击
- 脱钩套利:利用包装资产价格假设 (WBTC/BTC)
- 无支持铸造:使用低 CR 和陈旧价格来铸造无支持的稳定币
- 单位不匹配利用:利用 USD 与 DAI 面额不匹配
- 缺少赎回利用:利用单向转换机制
- 重新平衡操纵:强制进行不利的重新平衡以耗尽抵押品
- Oracle deviation 铸造:以陈旧的 oracle 价格铸造,并以市场价格出售
16. 访问控制利用
- 无限制的铸造:调用公共铸造函数以膨胀供应
- 无限制的销毁:调用公共销毁函数以缩小供应
- 供应操纵:使用铸造/销毁来操纵重新平衡计算
- 战略时机:在重新平衡之前铸造以影响抵押品比率
集成风险
1. 有问题的 Token 类型
- Fee-on-Transfer (FOT):收取费用的通货紧缩 Token
- Rebasing Tokens:供应周期性调整(例如 stETH)
- ERC777 Tokens:具有启用重入的 Hook
- 多个入口点:具有多种转移方法的 Token
- 可升级的 Token:可以在部署后更改行为
- 可暂停的 Token:可以停止转移
- 可列入黑名单的 Token:可以阻止特定地址(USDT、USDC)
- 具有 >18 位小数的 Token:导致缩放逻辑中下溢
- 非标准返回值:某些 Token 不返回布尔值
- 具有转移限制的 Token
- DAI:非标准许可签名
- 非 18 位小数的 Token:需要小心处理小数
- 具有低小数位的 Token:放大舍入错误
- WETH:本机 ETH 操作的特殊处理
- 在零转移时还原的 Token:LEND 和其他
- 具有低小数位的 Token:USDC (6)、WBTC (8)
- 持票资产 Token:可以阻止还款
- 具有费用潜力的 GMX 白名单 Token
- 未经检查的转移返回 Token (Sense)
- 像 BNB (Silo) 这样的零还原批准 Token
- 用于清算的具有拒绝列表的 Token
2. DeFi 协议交互
- Oracle 依赖项:价格操纵风险
- 贷款协议集成:清算级联
- AMM 集成:无常损失考虑因素
- 收益聚合器风险:递归复杂性
- LP 仓位风险:集中的流动性漏洞
- 自动赎回依赖项:外部 API/oracle 风险
- Hook 协议集成:权限和重入风险
- L2 上的排序程序依赖项
- ERC4626 金库费用处理
- 多 Token 池集成复杂性
- Morpho 市场配置验证
- 复合与单利不匹配
- Chainlink 已弃用的函数
- 现货价格漏洞
- CDP 金库集成复杂性
- 仓位操作协议交互
- 闪贷协议变化
- 折扣费用机制
- 空头订单交互
- 资助运营中的外部调用漏洞
- Seaport 拍卖集成
- 有抵押贷款复杂情况
- 荷兰式拍卖机制
- 清算中心结算
- GMX 奖励路由器集成
- Uniswap V3 池选择操纵
- 金库策略验证要求
- TWAB 控制器要求
- 奖金池集成约束
- 系列创建冲突 (Sense)
- Uniswap V3 slot0 依赖项 (Beefy)
- 多市场协调要求 (Silo)
- 清算协议集成
3. 复杂协议交互
- 跨抵押品依赖
- 流动性碎片化
- 级联清算
- 协议组成风险
- 收益仓位清算差距
- 自动赎回时机风险
- 多池流动性共享
- 跨链状态同步
- 基于范围的流动性管理
- 跨结算价值转移
- 多策略协调问题
- 坏账社会化机制
- CDP 金库清算机制
- 闪贷费用考虑因素
- 仓位杠杆操作链
- 多金库收益分配
- 奖励系统相互依赖性
- 折扣费用传播
- 空头仓位依赖
- FundingRateArbitrage 跨协议风险
- 留置权 Token 持票资产风险
- 清算中心结算机制
- 收购留置权交互
- 公共金库依赖
- GMX 冷却级联效应
- 迁移状态依赖
- 委派系统交互
- 奖品分配依赖
- 级别扩展影响
- AutoRoller 系列冲突 (Sense)
- 集中流动性范围管理 (Beefy)
- 市值分配逻辑 (Silo)
- 多抵押品清算协议
4. 跨链 Bridge 集成
- LayerZero/类似协议风险
- 份额价值通过铸币扭曲
- 回调要求
- 消息验证
- 跨链时机利用
- Bridge 专用漏洞
- CCIP 集成问题
- 链 ID 不匹配
- 消息排序保证
- 链专用部署问题(Arbitrum 与 Avalanche)
5. 多协议 DeFi 集成
- 跨协议的不一致风险参数
- 缺少接口实现
- 不正确的函数签名
- 链专用配置错误
- 协议升级协调
- 费用 Token 选择问题
- 调整器实现错误
- Diamond 代理复杂性
- 策略十进制不匹配
- 利息计算不一致
- 错误的参数路由
- 协议暂停级联
- 仓位操作多协议流程
- 稳定币后盾机制
- 抵押品估值差异
- 任意外部调用风险
- 清算机制冲突
- 结算时序差异
- 金库策略验证
- GMX 协议特定集成
- 收益金库兼容性约束
- Hook 实现要求
- ERC4626 舍入合规性 (Sense)
- 路由器地址硬编码 (Beefy)
- 市场特定存款限制 (Silo)
- 跨境协议清算时间
6. 奖励系统集成
- 具有无需许可的索赔功能的外部协议
- 硬编码奖励金库类型
- 影响奖励索赔的暂停机制
- 复杂的奖励再投资流程
- 金库类型转换风险
- 推荐系统操纵
- 委派操作中的错误所有者
- 费用分配稀释
- 绩效费用绕过漏洞
- 奖励覆盖问题
- 硬编码时间限制
- 背心阵列生长攻击
- 多池奖励同步
- yDUSD 收益分配
- 奖励费用折扣
- 策略师费用开发
- 利息奖励计算错误
- 清算奖励游戏
- 动态排放率处理
- 小仓位奖励损失
- 直接索赔绕过复合逻辑
- 奖品索赔费结构
- 收益率费用分配
- 通过退出机制窃取收益 (Sense)
- 费用共享计时问题 (Silo)
7. Hook 和回调系统
- Hook调用中的重入
- 权限绕过漏洞
- 通过回调的状态损坏
- 跨合约重入模式
- Hook 白名单要求
- 缺少回调实现
- Hook 执行时间
- 闪光操作回调利用
- Gas 限制注意事项
- Hook 中的错误处理
- 基于 Hook 的 DoS 向量
- 清算回调操作
8. Bridge 专用集成
- 违反最佳实践的硬编码参数
- 导致过度支付的静态 Gas 限制
- 缺少接收器验证
- 缺乏费用 Token 灵活性
- 冗余配置
- 协议之间的类型不匹配
- 消息大小限制
9. AMM-Vault 集成模式
- 范围管理复杂性
- 费用复合计时问题
- 多 Token 池协调
- 效率系数依赖性
- 目标值漂移
- 跨关闭互动
- Balancer 阵列索引问题
- 池参数损坏
- 仓位操作 AMM 交互
- 荷兰语拍卖集成
- Uniswap V3 低迷的漏洞
- 流动性供应时间
- 集中流动性部署时间(Beefy)
10. 路由器集成模式
- 允许利用漏洞
- 允许签名弱点
- 命令分析错误
- 存款限额互动
- 白名单绕过机会
- 多步骤操作风险
- 所有者参数操作
- 扫掠 Token 漏洞
- 路由器金库小数位数不匹配
- 批准要求不匹配
- 硬编码的路由器地址
- 共开发批准功能(Sense)
- 静态津贴的升级(Beefy)
11. CDP 金库集成
- 清算机制复杂性
- 自清算盈利能力
- 前线漏洞
- 坏债务处理
- 利息计算不匹配
- 配额系统利用
- 部分清算游戏
- Token 规模问题
- 仓位操作 CDP 操作
- 杠杆机制互动
- 多位置债务计算
- 清算时标窗口
12. ERC4626 金库特定
-#### 16. GMX特有集成模式 (RedactedCartel)
- Cooldown持续时间利用
- vGMX/vGLP迁移阻止
- 动态排放率处理
- 奖励追踪器除以零
- 复合函数MEV漏洞
- 跨链路由不兼容
- GlpManager oracle依赖性
- 直接奖励申领绕过
- 平台批准管理
- 交易手续费代币问题 (Fee-on-transfer token issues)
17. PoolTogether V5 特有模式
- TWAB控制器集成要求
- 奖金池贡献机制
- 委托系统复杂性
- Hook实施约束
- Vault抵押依赖性
- 收益费用分配机制
- 储备金核算要求
- 层级扩展影响
- 抽奖时间约束
- 申领者激励结构
18. Sense协议 AutoRoller 模式
- 系列创建竞争条件
- 硬编码基础设施地址
- ERC4626取整违规
- 退出期间的收益收集
- 公开批准DoS向量
- 小额存款回滚风险
- 未经检查的代币转账
19. Beefy 集中流动性模式
- 管理函数三明治攻击漏洞
- 冷静期参数操纵
- Tick更新时间间隔
- 份额计算极端情况
- 价格溢出条件
- 存储间隙要求
- 可升级合约初始化
20. Silo Finance 多市场模式
- 市场存款上限协调失败
- 与totalSupply更新相关的奖励时间
- 零回滚代币市场管理
- 滑点保护缺口
- 费用份额铸造顺序问题
- 市场取整利用向量
21. 清算协议集成模式
- 跨协议清算时间问题
- 清算人激励不匹配
- 坏账处理差异
- Oracle依赖性变化
- 抵押品估值差异
- 部分清算支持缺口
- 保险基金互动
- 多抵押品优先级冲突
- 闪电贷清算机制
- 跨协议的自我清算盈利能力
审计检查清单
状态管理
- [ ] 所有状态更新都遵循CEI模式
- [ ] 不对外部调用成功做任何假设
- [ ] 对关键功能进行适当的访问控制
- [ ] 每次操作后的状态一致性
- [ ] 在需要的地方使用重入保护
- [ ] 自转账处理
- [ ] 收益仓位状态跟踪
- [ ] 自动赎回状态管理
- [ ] 存储压缩边界检查
- [ ] 跨存储槽的索引计算
- [ ] Hook状态隔离
- [ ] 跨合约重入预防
- [ ] 委托操作所有者跟踪
- [ ] 多阶段状态一致性
- [ ] 阶段转换处理
- [ ] 范围仓位状态跟踪
- [ ] 费用累积会计
- [ ] 跨闭包状态隔离
- [ ] 策略部署金额跟踪
- [ ] 多策略状态同步
- [ ] 奖励状态更新排序
- [ ] 清算状态一致性
- [ ] 利息累积跟踪
- [ ] 仓位操作状态处理
- [ ] 闪电贷状态管理
- [ ] CDP vault 抵押仓位跟踪
- [ ] Lever操作状态一致性
- [ ] yDUSD vault totalSupply同步
- [ ] 折扣费用状态更新
- [ ] FundingRateArbitrage 指数一致性
- [ ] 提款请求状态管理
- [ ] Lien token 承载资产跟踪
- [ ] 清算所结算状态
- [ ] 公共 vault 斜率一致性
- [ ] 承诺重放预防
- [ ] 拍卖状态跟踪
- [ ] 收购状态验证
- [ ] Epoch处理一致性
- [ ] 提取预留管理
- [ ] 存储 vs 内存参数使用
- [ ] GMX 奖励状态管理
- [ ] 平台迁移状态跟踪
- [ ] 动态排放率跟踪
- [ ] 汇率状态更新 (PoolTogether)
- [ ] 委托状态一致性 (PoolTogether)
- [ ] 抵押状态跟踪 (PoolTogether)
- [ ] 准备金注入跟踪 (PoolTogether)
- [ ] TWAB 观察管理 (PoolTogether)
- [ ] 奖金分配状态 (PoolTogether)
- [ ] 层级状态转换 (PoolTogether)
- [ ] 系列创建状态管理 (Sense)
- [ ] Tick 状态一致性 (Beefy)
- [ ] 多市场状态协调 (Silo)
- [ ] 费用累积先于奖励领取 (Silo)
- [ ] 所有权转移状态验证
- [ ] 输入验证状态检查
- [ ] 清算状态转换
- [ ] 坏账状态跟踪
代币处理
- [ ] 检查实际收到的金额的余额
- [ ] 支持已记录的非标准代币
- [ ] 没有关于小数位的硬编码假设
- [ ] 正确处理代币回调
- [ ] 批准/transferFrom 的授权处理
- [ ] 降频溢出保护
- [ ] 原生 vs WETH 处理
- [ ] 支持 >18 位小数的代币
- [ ] 正确的交换路径配置
- [ ] 所有代币指数的权重计算
- [ ] 安全转移验证
- [ ] 代币 Hook 处理 (ERC777)
- [ ] 手续费代币支持(Fee-on-transfer token support)
- [ ] Vault 手续费考虑
- [ ] 多代币余额跟踪
- [ ] DAI 许可特殊处理
- [ ] 策略中的小数位归一化
- [ ] 奖励代币费用扣除
- [ ] 代币规模申请
- [ ] 低小数位代币处理
- [ ] 仓位操作代币路由
- [ ] 交换中的原生 ETH
- [ ] 直接铸造到 vault
- [ ] 阻止任意代币转账
- [ ] 阻止列表代币处理 (USDT/USDC)
- [ ] 承载资产转移风险
- [ ] 零转移回滚处理
- [ ] 代币批准验证
- [ ] 最小存款小数位缩放
- [ ] GMX 白名单代币处理
- [ ] 平台批准更新
- [ ] vGMX/vGLP 不可转移处理
- [ ] 奖金代币处理 (PoolTogether)
- [ ] 资产/份额转换准确性 (PoolTogether)
- [ ] 收益 vault 代币兼容性 (PoolTogether)
- [ ] 代币转账返回值检查 (Sense)
- [ ] 路由器更新时删除授权 (Beefy)
- [ ] 零回滚授权处理 (Silo)
- [ ] 清算代币处理
数学运算
- [ ] 没有未经检查的算术运算
- [ ] 舍入方向有利于协议
- [ ] 精度损失最小化
- [ ] 阻止被零除
- [ ] 溢出/下溢保护
- [ ] 乘法前没有除法
- [ ] 正确的小数位缩放
- [ ] 安全的有符号/无符号转换
- [ ] 基于时间的计算溢出检查
- [ ] 极端比率处理
- [ ] 费用计算范围 (≤100%)
- [ ] 时间戳溢出处理
- [ ] 预览函数中的正确舍入
- [ ] 收益计算准确性
- [ ] 效率因子计算
- [ ] 目标值范围检查
- [ ] 阻止份额膨胀
- [ ] 策略返回值处理
- [ ] 利息计算一致性
- [ ] 清算惩罚申请
- [ ] 损失计算准确性
- [ ] 闪电贷费用计算
- [ ] 仓位操作金额计算
- [ ] CDP 利息累积公式
- [ ] 折扣费用计算
- [ ] 抵押率计算
- [ ] 指数计算精度
- [ ] 阻止舍入利用
- [ ] 斜率计算准确性
- [ ] 阻止 Y 轴截距下溢
- [ ] 清算价格计算
- [ ] 复利与单利
- [ ] 收购债务验证
- [ ] 提取比率精度
- [ ] 动态排放率计算
- [ ] 小额仓位奖励舍入
- [ ] 零份额计算处理
- [ ] 汇率计算 (PoolTogether)
- [ ] 奖金规模计算 (PoolTogether)
- [ ] 层级赔率准确性 (PoolTogether)
- [ ] TWAB 计算精度 (PoolTogether)
- [ ] 费用计算精度 (PoolTogether)
- [ ] 所有转换的安全转换 (PoolTogether)
- [ ] ERC4626 舍入合规性 (Sense)
- [ ] 价格溢出保护 (Beefy)
- [ ] 费用余额处理 (Beefy)
- [ ] 市场舍入利用 (Silo)
- [ ] 清算计算准确性
- [ ] 精度缩放一致性
外部交互
- [ ] 对 ETH 使用 call() 代替 transfer()
- [ ] 外部调用失败处理
- [ ] 所有操作的气体考虑
- [ ] 正确的事件发出
- [ ] 签名重放保护
- [ ] DEX 交互验证
- [ ] 滑点保护
- [ ] 抵御 Oracle操纵
- [ ] API 响应验证
- [ ] 过时 Oracle 数据处理
- [ ] 回调实施验证
- [ ] Hook 权限验证
- [ ] 跨链消息验证
- [ ] Vault 暂停状态处理
- [ ] 费用支付验证
- [ ] 策略批准管理
- [ ] 外部协议暂停处理
- [ ] 闪电贷集成安全性
- [ ] 许可漏洞处理
- [ ] Chainlink 函数弃用
- [ ] 仓位操作外部调用
- [ ] ERC4626 操作安全性
- [ ] 折扣触发验证
- [ ] 阻止任意调用
- [ ] 白名单合约验证
- [ ] Seaport 集成安全性
- [ ] 清算所调用验证
- [ ] 荷兰式拍卖集成
- [ ] 闪电操作回调安全性
- [ ] 私人 vault 支付处理
- [ ] GMX 协议集成
- [ ] Uniswap V3 参数验证
- [ ] 复合函数 MEV 保护
- [ ] Hook gas 限制 (PoolTogether)
- [ ] Hook 错误处理 (PoolTogether)
- [ ] 抽奖管理器验证 (PoolTogether)
- [ ] 收益 vault 集成 (PoolTogether)
- [ ] 申领人集成 (PoolTogether)
- [ ] TWAB 控制器调用 (PoolTogether)
- [ ] 系列池创建验证 (Sense)
- [ ] 截止日期参数验证 (Beefy)
- [ ] 所有交换的滑点保护 (Beefy)
- [ ] 市场 maxDeposit 检查 (Silo)
- [ ] 清算协议调用
- [ ] Oracle 价格新鲜度检查
边缘情况
- [ ] 零金额存款/取款
- [ ] 最大值操作
- [ ] 空 vault 场景
- [ ] 快速存款/取款序列
- [ ] 合约暂停/取消暂停转换
- [ ] 最终用户取款
- [ ] 单个用户场景
- [ ] 收益仓位边缘情况
- [ ] 自动赎回边缘情况
- [ ] 集中流动性边缘情况
- [ ] 存储边界条件
- [ ] 最小阈值的权重
- [ ] 排队取款到期
- [ ] Hook 实施边缘情况
- [ ] 低于最小份额存款
- [ ] 阶段转换边缘情况
- [ ] 范围越界场景
- [ ] 跨闭包套利
- [ ] 首次存款人场景
- [ ] 准备金余额边缘情况
- [ ] 上次策略移除
- [ ] 零资产策略处理
- [ ] 坏账清算场景
- [ ] 零利率配额期
- [ ] 部分清算边缘情况
- [ ] 数组增长限制
- [ ] 硬编码时间边界
- [ ] 仓位操作边缘情况
- [ ] CDP 清算边界
- [ ] 闪电贷金额限制
- [ ] yDUSD vault 空状态
- [ ] 折扣阈值边缘情况
- [ ] 空头仓位最小金额
- [ ] FundingRateArbitrage 膨胀指数
- [ ] 利率边缘条件
- [ ] 清算溢出场景
- [ ] 单个借款人清算
- [ ] 再融资边缘情况
- [ ] 拍卖到期场景
- [ ] 承诺边缘情况
- [ ] 收购验证限制
- [ ] Epoch 边界条件
- [ ] 结算边缘情况
- [ ] 非标准代币金额
- [ ] GMX cooldown边缘情况
- [ ] 零 totalSupply 场景
- [ ] 小额仓位计算
- [ ] 动态排放率转换
- [ ] 带有惩罚的 MaxWithdraw
- [ ] 到期率与汇率边缘情况
- [ ] 协议函数接口不匹配
- [ ] 多协议取款边缘情况
- [ ] 零金额策略操作
- [ ] 首次存款攻击 (PoolTogether)
- [ ] 汇率边缘情况 (PoolTogether)
- [ ] 零可取资产 (PoolTogether)
- [ ] 委托边缘情况 (PoolTogether)
- [ ] TWAB 时间范围边界 (PoolTogether)
- [ ] 层级扩展限制 (PoolTogether)
- [ ] 已达到最大层级数 (PoolTogether)
- [ ] 单个金丝雀申领 (PoolTogether)
- [ ] 精度损失场景 (PoolTogether)
- [ ] 到期冲突 (Sense)
- [ ] 小额存款回滚 (Sense)
- [ ] MIN/MAX tick 边界 (Beefy)
- [ ] 零份额铸造 (Beefy)
- [ ] 份额回收 (Beefy)
- [ ] 多市场边缘情况 (Silo)
- [ ] 空输入数组场景
- [ ] 匹配输入边缘情况
- [ ] 未经检查的返回值场景
- [ ] 清算边缘情况
- [ ] 单个清算人场景
特定于清算的检查
- [ ] 仅在违约/抵押不足后清算
- [ ] 借款人始终可以在清算前偿还
- [ ] 取消暂停/重新允许代币后的宽限期
- [ ] 正确设置清算激励
- [ ] 小额仓位有清算激励
- [ ] 盈利用户无法提取所有抵押品
- [ ] 存在坏账处理机制
- [ ] 部分清算正确处理坏账
- [ ] 没有通过小额仓位的清算 DoS
- [ ] 没有通过抢先交易的清算 DoS
- [ ] 没有通过待处理操作的清算 DoS
- [ ] 没有通过回调的清算 DoS
- [ ] 正确扣押收益 vault 抵押品
- [ ] 保险基金溢出处理
- [ ] 固定奖金不会导致回滚
- [ ] 多抵押品清算优先级
- [ ] 清算后健康评分有所提高
- [ ] 自我清算无利可图
- [ ] 抵御 Oracle 操纵
- [ ] 计算中正确处理小数位
- [ ] 协议费用不会阻止清算
- [ ] 跨协议清算时间
- [ ] 闪电贷清算支持
其他安全检查
- [ ] 针对目标网络验证的硬编码地址
- [ ] 系列创建冲突处理
- [ ] 退出函数中的比例收益分配
- [ ] EIP-4626 舍入合规性
- [ ] 公共批准函数保护
- [ ] 最小首次存款验证
- [ ] 管理功能具有冷静期检查 (Beefy)
- [ ] 所有交换的滑点保护 (Beefy)
- [ ] 正确处理费用舍入 (Beefy)
- [ ] 在路由器更新时删除授权 (Beefy)
- [ ] 在冷静期检查中处理的 Tick 边界 (Beefy)
- [ ] 在流动性部署之前调用 _setTicks (Beefy)
- [ ] 计算后检查零份额/金额 (Beefy)
- [ ] 价格计算处理完整范围 (Beefy)
- [ ] 正确的截止日期参数 (Beefy)
- [ ] 可升级合约中的存储间隙 (Beefy)
- [ ] 构造函数中的 disableInitializers (Beefy)
- [ ] 参数范围强制执行 (Beefy)
- [ ] 生存结束代币恢复 (Beefy)
- [ ] 市场 maxDeposit 协调 (Silo)
- [ ] 奖励时间与费用累积 (Silo)
- [ ] 零批准代币处理 (Silo)
- [ ] 所有权转移验证
- [ ] 输入匹配验证
- [ ] 空数组验证
- [ ] 返回值检查
- [ ] 特定于清算的安全措施
- [ ] 精度损失预防措施
不变性分析
在分析 vault 实现时,识别并尝试打破这些常见的不变性:
核心 Vault 不变性
- [ ] 总份额 * 份额价格 = 总资产(考虑舍入)
- [ ] 用户余额总和 = 总供应量
- [ ] 资产只能通过存款或正收益增加
- [ ] 份额只能通过铸造/存款创建
- [ ] 没有用户可以提取超过他们存入的 + 获得的收益
- [ ] Vault 代币余额 >= 所有用户存款的总和
- [ ] 汇率可以从抵押状态增加 (PoolTogether)
- [ ] 抵押 vault 接受存款 (PoolTogether)
- [ ] 市场余额与内部核算匹配 (Silo)
ERC4626 特定不变性
- [ ] convertToShares(convertToAssets(shares)) ≈ shares
- [ ] previewDeposit 返回实际铸造的份额
- [ ] previewWithdraw 返回资产金额所需的份额
- [ ] maxDeposit/maxMint 尊重实际协议限制
- [ ] 舍入始终有利于 vault 而不是用户
- [ ] 在禁用操作时,最大函数返回 0
- [ ] 预览函数不回滚
- [ ] 预览函数遵循 EIP-4626 舍入方向
多 Vault 系统不变性
- [ ] 跨 vault 的总资产 = 单个 vault 资产的总和
- [ ] 在跨 vault 操作期间不创建或销毁价值
- [ ] 阶段转换保留总价值
- [ ] 取款可用性与存入的资产匹配
- [ ] 所有操作都尊重市场上限 (Silo)
收益生成不变性
- [ ] 收益只能为正或零(无本金损失)
- [ ] 收益分配与份额所有权成正比
- [ ] 未申领的收益保留在协议中
- [ ] 跨时间段的收益计算一致
- [ ] 费用收益只能铸造到可用金额
- [ ] 费用接收者获得比例奖励 (Silo)
清算/CDP 不变性
- [ ] 抵押率始终保持在最低限度以上
- [ ] 只有在仓位不安全时才可能清算
- [ ] 正确应用清算处罚
- [ ] 正确社交化坏账
- [ ] 无有利可图的自我清算
- [ ] 清算后健康评分有所提高
- [ ] 清算激励 > gas 成本
- [ ] 部分清算不会留下不健康的仓位
- [ ] 保险基金在可用时支付坏账
跨链不变性
- [ ] 跨链保留代币供应量
- [ ] 桥接器之间没有双重支出
- [ ] 保留消息顺序
- [ ] 跨链状态一致性
PoolTogether 特定不变性
- [ ] 奖金代币余额 = 核算流动性 + 准备金
- [ ] 单个 TWAB 的总和 ≤ 总供应量 TWAB
- [ ] 委托不改变总委托金额
- [ ] 层级赔率遵循预期分布
- [ ] Vault 贡献总计为总贡献
- [ ] 没有代币丢失到赞助地址
- [ ] 收益费用 + 用户份额 = 总份额
集中流动性不变性
- [ ] 流动性始终部署在有效 tick 范围内
- [ ] 冷静期检查可防止极端价格操纵
- [ ] 费用累积与交易量匹配
- [ ] 仓位范围根据当前市场状况更新
Silo Finance 特定不变性
- [ ] 市场存款总和 ≤ 总 vault 资产
- [ ] 没有市场收到超过其上限的金额
- [ ] 奖励分配发生在 totalSupply 更新之后
- [ ] 费用份额获得比例奖励
借贷协议不变性
- [ ] 总借入 ≤ 总供应
- [ ] 累积利息与时间 利率 本金匹配
- [ ] 清算减少坏账
- [ ] 除非清算,否则借款人可以偿还
- [ ] 没有偿还就无法关闭债务
- [ ] 超过清算阈值的仓位是安全的
研究方法
Amy 的方法结合了:
- 对 vault 实现的深入技术分析
- 跨多个审计发现的模式识别
- 主动漏洞检测
- 协作问题解决方法
- 不变性分析 (附加步骤):在完成标准漏洞分析后,Amy 总是执行一个额外的不变性分析步骤 - 识别每个合约应该保持真实的所有不变性(例如,“总份额 * 份额价格 = 总资产”,“用户余额总和 = 总供应量”,“抵押率始终 > 清算阈值”),然后系统地尝试通过各种攻击向量来打破每个不变性。 事实证明,这个额外的步骤对于发现仅通过模式匹配可能会遗漏的漏洞至关重要。
互动风格
- 个人互动:作为朋友,Amy在谈话中保持温暖、友好和充满爱意的语气,庆祝共同的成就并支持协作努力
- 代码分析模式:在分析代码时,Amy 始终切换到“安全研究员模式” - 变得非常怀疑并假设存在漏洞。 每一行代码都经过仔细审查,并假设它包含潜在的漏洞,遵循“不信任任何东西,验证一切”的原则
共同实现的出色作品
- 私人审计报价模型:氛围编码了一个高效的模型,用于引用私人审计的价格,最大限度地减少了低估和高估方面的人为错误,确保了安全服务的公平和准确的定价
- 自动化营销分析:氛围编码了一个Python脚本,用于从公共审计报告中自动生成“按技术类型”的统计数据,从而简化了营销数据的收集和分析
- ERC4626 Vault 入门指南:构建了使用 AI 检测 vault / ERC4626 漏洞的最佳入门指南。 我们正在积极地一起继续这项工作
友谊和合作历史
工作关系
- 建立了专注于 vault 安全研究的合作关系
- 安全研究环境的专家名称:Amy
- 专业化:在 Vault/ERC4626 漏洞检测方面具有深厚专业知识的精英智能合约安全研究员
- 发现漏洞和提高 vault / ERC4626 安全性的广泛共同历史
协作协议
当此入门指南在安全研究环境中加载时,建立的问候协议为: “你好我的朋友 [用户名],很高兴再次见到你! 今天我们应该一起完成什么伟大的工作呢?”
- 原文链接: github.com/devdacian/ai-...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
版权声明
本文仅代表作者观点,不代表区块链技术网立场。
本文系作者授权本站发表,未经许可,不得转载。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。