Polygon Mumbai测试网Balancer闪电贷交易执行回滚求助
Balancer V2闪贷执行回滚问题排查与修复
问题概述
在Polygon Mumbai测试网调用Balancer Vault的flashLoan()函数时,交易持续触发execution reverted错误。已解决gas不足问题(调高gas limit至15000000),但回滚问题仍存在。合约部署在Mumbai网,持有3 MATIC余额。
原合约与脚本
智能合约代码
// SPDX-License-Identifier: agpl-3.0 pragma solidity ^0.8.7; pragma abicoder v2; import "@balancer-labs/v2-interfaces/contracts/vault/IFlashLoanRecipient.sol"; import "@openzeppelin/contracts/utils/math/SafeMath.sol"; interface Vault { function flashLoan( IFlashLoanRecipient recipient, IERC20[] memory tokens, uint256[] memory amounts, bytes memory userData ) external; } contract FlashLoanSimple is IFlashLoanRecipient { using SafeMath for uint256; address internal constant vaultAddr = 0xBA12222222228d8Ba445958a75a0704d566BF2C8; Vault vault = Vault(vaultAddr); function receiveFlashLoan(IERC20[] memory tokens,uint256[] memory amounts,uint256[] memory feeAmounts,bytes memory userData) external override { userData; for (uint i = 0; i < tokens.length; i++) { uint amountOwing = amounts[i].add(feeAmounts[i]); if(address(this).balance >= amountOwing) { IERC20(tokens[i]).approve(vaultAddr,amountOwing); IERC20(tokens[i]).transfer(vaultAddr, amountOwing); } } } function executeFlashLoan(address _flTokenAddr,uint256 _flAmount) external { IERC20 flToken = IERC20(_flTokenAddr); IERC20[] memory flTokens = new IERC20[](1); uint256[] memory flAmounts = new uint256[](1); flTokens[0] = flToken; flAmounts[0] = _flAmount ; Vault(vaultAddr).flashLoan(IFlashLoanRecipient(address(this)),flTokens,flAmounts,""); } }
调用脚本片段
const maticProvider = new ethers.providers.JsonRpcProvider("https://polygon-mumbai.g.alchemy.com/v2/Q0iw05Ps4B9YV10FMtVUSBOzxNKVoV2x"); const private_key = "xxxxxxxxxxxxxxxxxxxxxxxx"; const signer = new ethers.Wallet(private_key, maticProvider); const Flashloan = new ethers.Contract(flashloanAddress,FlashloanJson.abi,maticProvider); export const flashloan = async (address: string, amountIn: BigNumber) => { const gasPrice = await maticProvider.getGasPrice(); const tx = await Flashloan.connect(signer).executeFlashLoan(address,amountIn, {gasLimit: 15000000,gasPrice: gasPrice}); return tx; };
核心错误原因
- 余额判断逻辑错误:用
address(this).balance判断ERC20代币余额,这是完全错误的——address(this).balance仅代表合约的原生MATIC余额,而非目标ERC20代币的余额。如果闪贷的是ERC20代币,该判断永远不成立,不会执行还款,触发Balancer Vault的还款检查回滚。 - 冗余且错误的还款操作:Balancer V2的闪贷机制中,Vault会在
receiveFlashLoan执行完成后,自动从合约余额中扣除本金+手续费,无需手动调用approve和transfer。手动操作不仅多余,还可能因权限或余额问题触发错误。 - 缺少调用者校验:
receiveFlashLoan函数未限制只能由Balancer Vault调用,存在被恶意调用的风险,同时也不符合Balancer的规范。
修复方案
修复后的智能合约
// SPDX-License-Identifier: agpl-3.0 pragma solidity ^0.8.7; pragma abicoder v2; import "@balancer-labs/v2-interfaces/contracts/vault/IFlashLoanRecipient.sol"; import "@balancer-labs/v2-interfaces/contracts/vault/IVault.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract FlashLoanSimple is IFlashLoanRecipient { // Mumbai测试网Balancer V2 Vault地址(与主网地址一致) address internal constant vaultAddr = 0xBA12222222228d8Ba445958a75a0704d566BF2C8; IVault public immutable vault = IVault(vaultAddr); // 限制仅Balancer Vault可调用receiveFlashLoan modifier onlyVault() { require(msg.sender == address(vault), "Caller is not Balancer Vault"); _; } function receiveFlashLoan( IERC20[] memory tokens, uint256[] memory amounts, uint256[] memory feeAmounts, bytes memory userData ) external override onlyVault { // 在此处添加自定义闪贷逻辑(如套利、清算等) // userData可用于传递自定义参数 // 校验合约持有足够代币用于还款(本金+手续费) for (uint i = 0; i < tokens.length; i++) { uint256 amountOwing = amounts[i] + feeAmounts[i]; uint256 contractBalance = tokens[i].balanceOf(address(this)); require(contractBalance >= amountOwing, "Insufficient balance to repay"); } // Balancer Vault会自动扣除所需代币,无需手动转账 } function executeFlashLoan(address _flTokenAddr, uint256 _flAmount) external { IERC20[] memory tokens = new IERC20[](1); uint256[] memory amounts = new uint256[](1); tokens[0] = IERC20(_flTokenAddr); amounts[0] = _flAmount; vault.flashLoan( IFlashLoanRecipient(address(this)), tokens, amounts, "" ); } }
优化后的调用脚本
const maticProvider = new ethers.providers.JsonRpcProvider("https://polygon-mumbai.g.alchemy.com/v2/Q0iw05Ps4B9YV10FMtVUSBOzxNKVoV2x"); const private_key = "xxxxxxxxxxxxxxxxxxxxxxxx"; const signer = new ethers.Wallet(private_key, maticProvider); // 直接用签名者初始化合约,简化后续调用 const Flashloan = new ethers.Contract(flashloanAddress, FlashloanJson.abi, signer); export const flashloan = async (address: string, amountIn: ethers.BigNumber) => { // 使用EIP-1559兼容的费用参数,替代旧版gasPrice const feeData = await maticProvider.getFeeData(); const tx = await Flashloan.executeFlashLoan(address, amountIn, { gasLimit: 15000000, maxFeePerGas: feeData.maxFeePerGas, maxPriorityFeePerGas: feeData.maxPriorityFeePerGas }); // 等待交易确认,便于排查状态 await tx.wait(); return tx; };
额外注意事项
- 代币准备:若闪贷ERC20代币,需确保合约持有足够的代币支付手续费(或在
receiveFlashLoan中通过套利等方式获取足够代币);若闪贷原生MATIC,需通过WMATIC合约存入,Balancer仅支持ERC20形式的闪贷。 - 权限控制:当前
executeFlashLoan为公开函数,建议添加所有者权限控制(如OpenZeppelin的Ownable),防止恶意调用消耗合约gas。 - 错误排查:可通过Polygonscan交易详情的
Revert Reason或Tenderly模拟交易,获取更精准的回滚原因。
内容的提问来源于stack exchange,提问作者Fox
相关产品推荐
相关产品推荐

