Balancer与UniswapV3跨闪电贷合约BAL#400重入错误排查求助
BAL#400重入错误分析与修复方案
错误原因
BAL#400是Balancer V2 Vault合约的重入保护触发提示。Balancer在执行闪电贷流程时,会通过_nonReentrant修饰符锁定Vault核心逻辑,receiveFlashLoan钩子函数完全处于该锁的执行周期内。此时若在钩子中再次调用Balancer的Vault或任何Balancer池合约(如swap、流动性操作),会直接触发重入检查,导致交易回滚。
修复方案
1. 重构套利路径,避免钩子内调用Balancer合约
将原本涉及Balancer的交易操作替换为其他DEX(如UniswapV3、SushiSwap等)的交互,确保receiveFlashLoan内仅执行非Balancer合约的调用。例如:
- 原错误逻辑:Balancer闪电贷借Token X → UniswapV3换Token Y → Balancer换Token X → 还款
- 修正逻辑:Balancer闪电贷借Token X → UniswapV3换Token Y → 其他DEX换Token X → 还款
2. 代码示例修正
假设原合约在钩子内调用了Balancer池的swap,修改后的核心代码如下:
// 正确的receiveFlashLoan实现 function receiveFlashLoan( IERC20[] memory tokens, uint256[] memory amounts, uint256[] memory feeAmounts, bytes memory userData ) external override { // 仅与UniswapV3交互,避免调用Balancer合约 (address router, address pool, uint24 fee, uint256 amountIn) = abi.decode(userData, (address, address, uint24, uint256)); // UniswapV3换出目标代币 ISwapRouter.ExactInputParams memory params = ISwapRouter.ExactInputParams({ path: abi.encodePacked(tokens[0], fee, tokens[1]), recipient: address(this), deadline: block.timestamp, amountIn: amounts[0], amountOutMinimum: 0 }); ISwapRouter(router).exactInput(params); // 反向换回原代币(使用另一DEX,示例仍用UniswapV3) ISwapRouter.ExactOutputParams memory reverseParams = ISwapRouter.ExactOutputParams({ path: abi.encodePacked(tokens[1], fee, tokens[0]), recipient: address(this), deadline: block.timestamp, amountOut: amounts[0] + feeAmounts[0], amountInMaximum: type(uint256).max }); ISwapRouter(router).exactOutput(reverseParams); // 归还闪电贷本金+手续费 tokens[0].transfer(msg.sender, amounts[0] + feeAmounts[0]); }
3. 额外检查项
- 确保还款金额计算正确:必须归还
amounts[i] + feeAmounts[i],缺一不可 - 验证合约权限:
receiveFlashLoan需限制仅Balancer Vault可调用(可通过检查msg.sender是否为Vault地址实现) - 避免其他重入风险:确保与外部DEX交互时,使用安全的调用方式(如配合OpenZeppelin的
ReentrancyGuard修饰符)
内容的提问来源于stack exchange,提问作者aprogrammingfan
相关产品推荐
相关产品推荐

