You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 04:45:39