本篇为《UniswapV2 深入解析》系列第 22 篇,延续上一章对闪电贷流程与手续费模型的讨论,聚焦于 闪电贷引入的重入风险 及其治理方案。阅读本文前,建议先回顾第 20 与 21 篇对手续费修复与闪电贷机制的梳理,以便理解本文中合约架构的演进脉络。
本篇为《UniswapV2 深入解析》系列第 22 篇,延续上一章对闪电贷流程与手续费模型的讨论,聚焦于 闪电贷引入的重入风险 及其治理方案。阅读本文前,建议先回顾第 20 与 21 篇对手续费修复与闪电贷机制的梳理,以便理解本文中合约架构的演进脉络。
swap
与闪电贷逻辑时,Pair 合约会在更新储备前提前转账,以支持“乐观转移”。swap
,即可重复提取代币,造成严重资金损失。data
参数触发借方回调,完全禁止外部调用会破坏核心功能。我们采用 Guard Check(进入/退出标志)来提供最小侵入的防护:
/// @notice UniswapV2 交易对核心合约
contract UniswapV2Pair is IUniswapV2Pair {
/// @dev 标记当前是否处于 swap 执行过程中
bool private entered;
/// @notice 发起兑换或闪电贷
/// @param amount0Out 以 token0 计的输出数量
/// @param amount1Out 以 token1 计的输出数量
/// @param to 接收资产的目标地址
/// @param data 额外的回调参数,非空则触发 `IUniswapV2Callee`
function swap(
uint256 amount0Out,
uint256 amount1Out,
address to,
bytes calldata data
) external nonReentrant {
_swap(amount0Out, amount1Out, to, data);
}
/// @dev 防止在同一事务内重复进入 swap
modifier nonReentrant() {
if (entered) revert ReentrancyGuard();
entered = true;
_;
entered = false;
}
}
entered
放在合约存储中,虽略增 Gas,但换来可读性与安全性。ReentrancyGuard()
自定义错误代替字符串信息。swap
内部委托 _swap
,将安全防护与业务逻辑解耦,便于后续扩展手续费或奖励机制。UniswapV2Library
完成,保持架构职责单一,避免重新实现比例计算等逻辑。使用项目提供的 ./scripts/test.sh
执行测试,日志会写入 logs/
目录。以下为示例测试合约片段:
/// @title 测试重入攻击是否被阻止
contract UniswapV2PairReentrancyTest is Test {
UniswapV2Pair pair;
MaliciousCallee attacker;
function setUp() public {
// 1. 部署 Pair 与测试代币
// 2. 初始化储备,确保存在可借出的流动性
// 3. 部署伪造回调合约 `MaliciousCallee`
}
function testCannotReenterSwap() public {
vm.expectRevert(ReentrancyGuard.selector);
attacker.executeFlashLoan(address(pair));
}
}
测试步骤建议:
executeFlashLoan
中触发 pair.swap
,再尝试递归调用自身。vm.expectRevert
捕获 ReentrancyGuard
错误,确认防护生效。swap
过程中可能触发的内部函数都不应再次修改 entered
,以免状态错乱。如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!