从 Solv Protocol 273 万美元被黑事件,深入理解 Solidity 自重入攻击 —— ERC-3525 标准中的暗坑

2026 年 3 月 5 日,Solv Protocol 遭到攻击,损失 273 万美元 (38.0474 SolvBTC)。攻击者仅用 一笔交易,将 135 个 BRO 代币经过 22 次循环操作,膨胀为 5.67 亿 BRO,最终通过 BRO→SolvBTC→WBTC→WETH→ETH 的路径在 Uniswap V3 兑换成 1,211 ETH。

没有闪电贷,没有预言机操纵,没有复杂的跨链利用。就是最朴素的 Solidity 重入攻击。

但这件事之所以值得单独拿出来写,是因为它暴露了一个被严重忽视的风险面:新兴代币标准 ERC-3525 的继承体系带来的安全问题,以及"审计覆盖"这个概念本身可能存在的系统性盲区。

攻击发生了什么

项目
攻击者 EOA 0xa407fe273db74184898cb56d2cb685615e1c0d6e
受害合约 0x014e6F6ba7a9f4C9a51a0Aa3189B5c0a21006869
攻击交易 TX 0x44e637c7d85190d376a52d89ca75f2d208089bb02b7c4708ad2aaae3a97a958d
损失 38.0474 SolvBTC(约 273 万美元)

攻击的核心逻辑极其简洁:

  1. 攻击者调用受害合约的 mint() 函数,存入质押品并转入 NFT
  2. NFT 转入时触发 ERC-721 的 onERC721Received 回调
  3. 回调函数里执行了 _mint()------第一次铸造
  4. 回调返回后,mint() 函数继续执行 _mint()------第二次铸造

同一笔质押品,铸了两次。重复 22 次,135 个代币变成 5.67 亿个。

漏洞代码深度拆解

scss 复制代码
function mint(address to, uint256 collateralAmount, uint256 nftId) external {
    collateralToken.safeTransferFrom(msg.sender, address(this), collateralAmount);
    nftToken.safeTransferFrom(msg.sender, address(this), nftId); // 触发回调!
    _mint(to, collateralAmount);
}

function onERC721Received(address, address from, uint256, bytes calldata data) 
    external returns (bytes4) {
    uint256 amount = _parseCollateralAmount(data);
    _mint(from, amount); // 在回调里就 mint 了!
    return this.onERC721Received.selector;
}

执行流程:

scss 复制代码
攻击者调用 mint(to, 100, nftId)
  ├─ safeTransferFrom(collateralToken, 100)
  ├─ safeTransferFrom(nftToken, nftId)
  │    └─ 触发 onERC721Received()
  │         └─ _mint(attacker, 100)       // 第一次 mint
  └─ _mint(attacker, 100)                 // 第二次 mint

为什么叫"自"重入?

传统重入是 A 合约调用 B 合约,B 回调 A。而自重入是 A 合约在同一函数执行过程中,通过内部回调再次进入同一个合约的另一个函数

这种攻击往往比跨合约重入更隐蔽,因为外部调用看起来是正常的转账操作,回调函数是标准要求的,开发者会觉得"能有什么问题"。

ERC-3525:半同质化代币标准的暗坑

ERC-3525 是半同质化代币 标准,介于 ERC-20 和 ERC-721 之间。ERC-3525 继承了 ERC-721 ,包括 onERC721Received 回调接口。

如果你实现了 ERC-3525 合约,你的合约同时也是一个"能接收 NFT 的合约"。任何向你的合约转入 NFT 的操作,都会触发 onERC721Received 回调。

审计的系统性盲区

出问题的合约没有任何审计覆盖。Solv 有 5 家审计公司背书,但都没覆盖这个合约。Bug Bounty 也没有覆盖。

"经过审计"和"所有合约都经过审计"是两个完全不同的概念。

三种修复方案

方案一:ReentrancyGuard

vbscript 复制代码
contract SolvPool is ReentrancyGuard {
    function mint(...) external nonReentrant { ... }
}

方案二:CEI 模式

scss 复制代码
function mint(...) external {
    _mint(to, collateralAmount);      // 先改状态
    collateralToken.safeTransferFrom(...);  // 后调用外部
    nftToken.safeTransferFrom(...);
}

方案三:回调函数中不做 mint

kotlin 复制代码
function onERC721Received(...) external returns (bytes4) {
    return this.onERC721Received.selector; // 只返回selector
}

给 Solidity 开发者的实用清单

  1. 所有外部调用都是潜在的回调入口safeTransferFrom 也一样危险
  2. 继承 ERC-721/ERC-3525 的合约,必须审查 onERC721Received
  3. 默认使用 CEI 模式,状态修改在前,外部调用在后
  4. 关键函数加 nonReentrant,成本极低,收益极高
  5. 不要迷信"审计过了" ,明确审计覆盖了哪些合约
  6. 用 Foundry 做 fuzz testing

参考链接

相关推荐
MicroTech20252 小时前
微算法科技(NASDAQ: MLGO)探索量子隐形传态区块链隐私保护签名技术,增强Web 3.0元宇宙环境的效率、安全性和真实性
科技·区块链
草原猫2 小时前
公链开发:从技术筑基到生态共生,重构数字信任基础设施
重构·区块链
小白的代码日记2 小时前
区块链分叉检测与回扫系统(Go语言)
人工智能·golang·区块链
Blockchina3 小时前
Web3项目开发全流程详解:从0到1搭建DApp架构(实战版)
架构·web3·区块链·perp dex
Blockchina1 天前
Web3金融革命:PerpDEX的深度解析
金融·web3·区块链·perp dex·永续去中心化交易所
Joy T1 天前
【Web3】Solidity收款合约初探与去中心化预言机(Chainlink)机制解析
web3·去中心化·区块链·预言机·chainlink·don·datafeed
fuzamei8881 天前
【市场观察】黄金、白银等金属回归“真实资产”,区块链价值的时代也在路上
区块链
CryptoPP2 天前
开发者指南:构建实时期货黄金数据监控系统
大数据·数据结构·笔记·金融·区块链
方向研究3 天前
集运指数欧线EC
区块链